From 5a5684db7497d9fa68a1d9959e36089fdd160754 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Thu, 18 Jun 2020 15:19:46 -0500
Subject: [PATCH 01/33] skip tests using hostDetailsPolicyResponseActionBadge
---
.../public/management/pages/endpoint_hosts/view/index.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index e0f797b14305..62a9466c2847 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -358,7 +358,7 @@ describe('when on the hosts page', () => {
});
});
- describe('when showing host Policy Response panel', () => {
+ describe.skip('when showing host Policy Response panel', () => {
let renderResult: ReturnType;
beforeEach(async () => {
renderResult = render();
From 69128e916728536f3907a93fd34c5662a93b4bfe Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Thu, 18 Jun 2020 17:09:44 -0400
Subject: [PATCH 02/33] [7.x] [CCR] Fix follower indices table not updating
after pausing (#69228) (#69562)
---
.../components/follower_indices_table/follower_indices_table.js | 2 +-
.../public/app/store/actions/follower_index.js | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js
index 183609355fdf..0c57b3f7330c 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js
@@ -66,7 +66,7 @@ export class FollowerIndicesTable extends PureComponent {
if (prevFollowerIndices !== followerIndices) {
return {
prevFollowerIndices: followerIndices,
- filteredClusters: getFilteredIndices(followerIndices, queryText),
+ filteredIndices: getFilteredIndices(followerIndices, queryText),
};
}
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js
index 1af5a95a29b9..d7f03931474e 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js
@@ -149,7 +149,6 @@ export const resumeFollowerIndex = (id) =>
scope,
handler: async () => resumeFollowerIndexRequest(id),
onSuccess(response, dispatch) {
- console.log('response', response);
/**
* We can have 1 or more follower index resume operation
* that can fail or succeed. We will show 1 toast notification for each.
From 216bfe3054e89da520ce9c4adba8bd6a6da0e29f Mon Sep 17 00:00:00 2001
From: "Christiane (Tina) Heiligers"
Date: Thu, 18 Jun 2020 14:17:03 -0700
Subject: [PATCH 03/33] Fixes home page welcome link (#69539) (#69564)
---
.../components/__snapshots__/welcome.test.tsx.snap | 6 +++---
src/plugins/home/public/application/components/welcome.tsx | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
index 1c67f332a12a..abe529b79889 100644
--- a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
+++ b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
@@ -141,7 +141,7 @@ exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
values={Object {}}
/>
{
id="home.dataManagementDisableCollection"
defaultMessage=" To stop collection, "
/>
-
+
{
id="home.dataManagementEnableCollection"
defaultMessage=" To start collection, "
/>
-
+
Date: Fri, 19 Jun 2020 00:37:15 +0300
Subject: [PATCH 04/33] Fix home page loading if telemetry plugin disabled
(#69394) (#69522)
* Fix home page loading
* Fix jest test, update telemetry mocks
---
.../__snapshots__/welcome.test.tsx.snap | 3 --
.../application/components/welcome.test.tsx | 35 +++++--------------
.../public/application/components/welcome.tsx | 7 ++--
src/plugins/telemetry/public/mocks.ts | 23 +++++++++---
src/plugins/telemetry/public/plugin.ts | 7 ++++
5 files changed, 40 insertions(+), 35 deletions(-)
diff --git a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
index abe529b79889..4e66fd9e14c8 100644
--- a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
+++ b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap
@@ -125,7 +125,6 @@ exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
values={Object {}}
/>
@@ -224,7 +223,6 @@ exports[`should render a Welcome screen with the telemetry disclaimer when optIn
values={Object {}}
/>
@@ -323,7 +321,6 @@ exports[`should render a Welcome screen with the telemetry disclaimer when optIn
values={Object {}}
/>
diff --git a/src/plugins/home/public/application/components/welcome.test.tsx b/src/plugins/home/public/application/components/welcome.test.tsx
index 1332e03ffdc8..701fab3af753 100644
--- a/src/plugins/home/public/application/components/welcome.test.tsx
+++ b/src/plugins/home/public/application/components/welcome.test.tsx
@@ -30,56 +30,39 @@ jest.mock('../kibana_services', () => ({
}));
test('should render a Welcome screen with the telemetry disclaimer', () => {
- const telemetry = telemetryPluginMock.createSetupContract();
- const component = shallow(
- // @ts-ignore
- {}} telemetry={telemetry} />
- );
+ const telemetry = telemetryPluginMock.createStartContract();
+ const component = shallow( {}} telemetry={telemetry} />);
expect(component).toMatchSnapshot();
});
test('should render a Welcome screen with the telemetry disclaimer when optIn is true', () => {
- const telemetry = telemetryPluginMock.createSetupContract();
+ const telemetry = telemetryPluginMock.createStartContract();
telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
- const component = shallow(
- // @ts-ignore
- {}} telemetry={telemetry} />
- );
+ const component = shallow( {}} telemetry={telemetry} />);
expect(component).toMatchSnapshot();
});
test('should render a Welcome screen with the telemetry disclaimer when optIn is false', () => {
- const telemetry = telemetryPluginMock.createSetupContract();
+ const telemetry = telemetryPluginMock.createStartContract();
telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
- const component = shallow(
- // @ts-ignore
- {}} telemetry={telemetry} />
- );
+ const component = shallow( {}} telemetry={telemetry} />);
expect(component).toMatchSnapshot();
});
test('should render a Welcome screen with no telemetry disclaimer', () => {
- // @ts-ignore
- const component = shallow(
- // @ts-ignore
- {}} telemetry={null} />
- );
+ const component = shallow( {}} />);
expect(component).toMatchSnapshot();
});
test('fires opt-in seen when mounted', () => {
- const telemetry = telemetryPluginMock.createSetupContract();
+ const telemetry = telemetryPluginMock.createStartContract();
const mockSetOptedInNoticeSeen = jest.fn();
- // @ts-ignore
telemetry.telemetryNotifications.setOptedInNoticeSeen = mockSetOptedInNoticeSeen;
- shallow(
- // @ts-ignore
- {}} telemetry={telemetry} />
- );
+ shallow( {}} telemetry={telemetry} />);
expect(mockSetOptedInNoticeSeen).toHaveBeenCalled();
});
diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx
index c3773f19a9a0..cacb507009c7 100644
--- a/src/plugins/home/public/application/components/welcome.tsx
+++ b/src/plugins/home/public/application/components/welcome.tsx
@@ -38,7 +38,6 @@ import { METRIC_TYPE } from '@kbn/analytics';
import { FormattedMessage } from '@kbn/i18n/react';
import { getServices } from '../kibana_services';
import { TelemetryPluginStart } from '../../../../telemetry/public';
-import { PRIVACY_STATEMENT_URL } from '../../../../telemetry/common/constants';
import { SampleDataCard } from './sample_data';
interface Props {
@@ -162,7 +161,11 @@ export class Welcome extends React.Component {
id="home.dataManagementDisclaimerPrivacy"
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our "
/>
-
+
;
+export type Setup = jest.Mocked;
+export type Start = jest.Mocked;
export const telemetryPluginMock = {
createSetupContract,
+ createStartContract,
};
function createSetupContract(): Setup {
const telemetryService = mockTelemetryService();
- const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
const setupContract: Setup = {
telemetryService,
- telemetryNotifications,
};
return setupContract;
}
+
+function createStartContract(): Start {
+ const telemetryService = mockTelemetryService();
+ const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
+
+ const startContract: Start = {
+ telemetryService,
+ telemetryNotifications,
+ telemetryConstants: {
+ getPrivacyStatementUrl: jest.fn(),
+ },
+ };
+
+ return startContract;
+}
diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts
index a363953978d7..3846e7cb96a1 100644
--- a/src/plugins/telemetry/public/plugin.ts
+++ b/src/plugins/telemetry/public/plugin.ts
@@ -38,6 +38,7 @@ import {
getTelemetrySendUsageFrom,
} from '../common/telemetry_config';
import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default';
+import { PRIVACY_STATEMENT_URL } from '../common/constants';
export interface TelemetryPluginSetup {
telemetryService: TelemetryService;
@@ -46,6 +47,9 @@ export interface TelemetryPluginSetup {
export interface TelemetryPluginStart {
telemetryService: TelemetryService;
telemetryNotifications: TelemetryNotifications;
+ telemetryConstants: {
+ getPrivacyStatementUrl: () => string;
+ };
}
export interface TelemetryPluginConfig {
@@ -115,6 +119,9 @@ export class TelemetryPlugin implements Plugin PRIVACY_STATEMENT_URL,
+ },
};
}
From 4aa3b7d11bf58aba2a21e37ac1160a864342cc61 Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Thu, 18 Jun 2020 16:11:09 -0600
Subject: [PATCH 05/33] [7.x] [Reporting] Prepare export type definitions for
Task Manager (#65213) (#69346)
* [Reporting] Prepare export type definitions for Task Manager (#65213)
# Conflicts:
# x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts
# x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts
# x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts
* fix unintentional change of data access
---
x-pack/plugins/reporting/public/plugin.tsx | 2 -
.../common/execute_job/decrypt_job_headers.ts | 4 +-
.../get_conditional_headers.test.ts | 16 +-
.../execute_job/get_conditional_headers.ts | 4 +-
.../execute_job/get_custom_logo.test.ts | 6 +-
.../common/execute_job/get_custom_logo.ts | 4 +-
.../common/execute_job/get_full_urls.test.ts | 8 +-
.../common/execute_job/get_full_urls.ts | 20 +-
.../execute_job/omit_blacklisted_headers.ts | 4 +-
.../server/export_types/csv/index.ts | 14 +-
.../export_types/csv/server/create_job.ts | 14 +-
.../csv/server/execute_job.test.ts | 266 +++++++++---------
.../export_types/csv/server/execute_job.ts | 21 +-
.../server/export_types/csv/types.d.ts | 4 +-
.../csv_from_savedobject/index.ts | 12 +-
.../server/create_job/create_job.ts | 106 -------
.../server/create_job/index.ts | 94 ++++++-
.../server/execute_job.ts | 24 +-
.../csv_from_savedobject/types.d.ts | 4 +-
.../server/export_types/png/index.ts | 14 +-
.../png/server/create_job/index.ts | 6 +-
.../png/server/execute_job/index.test.ts | 24 +-
.../png/server/execute_job/index.ts | 18 +-
.../server/export_types/png/types.d.ts | 4 +-
.../export_types/printable_pdf/index.ts | 14 +-
.../printable_pdf/server/create_job/index.ts | 4 +-
.../server/execute_job/index.test.ts | 24 +-
.../printable_pdf/server/execute_job/index.ts | 18 +-
.../export_types/printable_pdf/types.d.ts | 4 +-
.../reporting/server/lib/create_queue.ts | 6 +-
.../server/lib/create_worker.test.ts | 12 +-
.../reporting/server/lib/create_worker.ts | 2 +-
.../reporting/server/lib/enqueue_job.ts | 6 +-
.../server/lib/export_types_registry.ts | 34 +--
.../generate_from_savedobject_immediate.ts | 22 +-
.../server/routes/lib/get_document_payload.ts | 10 +-
.../reporting/server/routes/types.d.ts | 4 +-
x-pack/plugins/reporting/server/types.ts | 36 +--
38 files changed, 421 insertions(+), 468 deletions(-)
delete mode 100644 x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts
diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx
index fcaa295a45ec..aad3d9b026c6 100644
--- a/x-pack/plugins/reporting/public/plugin.tsx
+++ b/x-pack/plugins/reporting/public/plugin.tsx
@@ -155,8 +155,6 @@ export class ReportingPublicPlugin implements Plugin {
);
}
- // FIXME: only perform these actions for authenticated routes
- // Depends on https://github.com/elastic/kibana/pull/39477
public start(core: CoreStart) {
const { http, notifications } = core;
const apiClient = new ReportingAPIClient(http);
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts
index e5124c80601d..579b5196ad4d 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts
@@ -14,14 +14,14 @@ interface HasEncryptedHeaders {
// TODO merge functionality with CSV execute job
export const decryptJobHeaders = async <
JobParamsType,
- JobDocPayloadType extends HasEncryptedHeaders
+ ScheduledTaskParamsType extends HasEncryptedHeaders
>({
encryptionKey,
job,
logger,
}: {
encryptionKey?: string;
- job: JobDocPayloadType;
+ job: ScheduledTaskParamsType;
logger: LevelLogger;
}): Promise> => {
try {
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts
index 5d651ad5f8ae..030ced5dc4b8 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts
@@ -8,8 +8,8 @@ import sinon from 'sinon';
import { ReportingConfig } from '../../../';
import { ReportingCore } from '../../../core';
import { createMockReportingCore } from '../../../test_helpers';
-import { JobDocPayload } from '../../../types';
-import { JobDocPayloadPDF } from '../../printable_pdf/types';
+import { ScheduledTaskParams } from '../../../types';
+import { ScheduledTaskParamsPDF } from '../../printable_pdf/types';
import { getConditionalHeaders, getCustomLogo } from './index';
let mockConfig: ReportingConfig;
@@ -37,7 +37,7 @@ describe('conditions', () => {
};
const conditionalHeaders = await getConditionalHeaders({
- job: {} as JobDocPayload,
+ job: {} as ScheduledTaskParams,
filteredHeaders: permittedHeaders,
config: mockConfig,
});
@@ -64,14 +64,14 @@ test('uses basePath from job when creating saved object service', async () => {
baz: 'quix',
};
const conditionalHeaders = await getConditionalHeaders({
- job: {} as JobDocPayload,
+ job: {} as ScheduledTaskParams,
filteredHeaders: permittedHeaders,
config: mockConfig,
});
const jobBasePath = '/sbp/s/marketing';
await getCustomLogo({
reporting: mockReportingPlugin,
- job: { basePath: jobBasePath } as JobDocPayloadPDF,
+ job: { basePath: jobBasePath } as ScheduledTaskParamsPDF,
conditionalHeaders,
config: mockConfig,
});
@@ -94,14 +94,14 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav
baz: 'quix',
};
const conditionalHeaders = await getConditionalHeaders({
- job: {} as JobDocPayload,
+ job: {} as ScheduledTaskParams,
filteredHeaders: permittedHeaders,
config: mockConfig,
});
await getCustomLogo({
reporting: mockReportingPlugin,
- job: {} as JobDocPayloadPDF,
+ job: {} as ScheduledTaskParamsPDF,
conditionalHeaders,
config: mockConfig,
});
@@ -139,7 +139,7 @@ describe('config formatting', () => {
mockConfig = getMockConfig(mockConfigGet);
const conditionalHeaders = await getConditionalHeaders({
- job: {} as JobDocPayload,
+ job: {} as ScheduledTaskParams,
filteredHeaders: {},
config: mockConfig,
});
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts
index 6854f678aa97..7a50eaac80d8 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts
@@ -7,13 +7,13 @@
import { ReportingConfig } from '../../../';
import { ConditionalHeaders } from '../../../types';
-export const getConditionalHeaders = ({
+export const getConditionalHeaders = ({
config,
job,
filteredHeaders,
}: {
config: ReportingConfig;
- job: JobDocPayloadType;
+ job: ScheduledTaskParamsType;
filteredHeaders: Record;
}) => {
const { kbnConfig } = config;
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts
index bd6eb4644d87..c364752c8dd0 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts
@@ -6,7 +6,7 @@
import { ReportingCore } from '../../../core';
import { createMockReportingCore } from '../../../test_helpers';
-import { JobDocPayloadPDF } from '../../printable_pdf/types';
+import { ScheduledTaskParamsPDF } from '../../printable_pdf/types';
import { getConditionalHeaders, getCustomLogo } from './index';
const mockConfigGet = jest.fn().mockImplementation((key: string) => {
@@ -37,7 +37,7 @@ test(`gets logo from uiSettings`, async () => {
});
const conditionalHeaders = await getConditionalHeaders({
- job: {} as JobDocPayloadPDF,
+ job: {} as ScheduledTaskParamsPDF,
filteredHeaders: permittedHeaders,
config: mockConfig,
});
@@ -45,7 +45,7 @@ test(`gets logo from uiSettings`, async () => {
const { logo } = await getCustomLogo({
reporting: mockReportingPlugin,
config: mockConfig,
- job: {} as JobDocPayloadPDF,
+ job: {} as ScheduledTaskParamsPDF,
conditionalHeaders,
});
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts
index 85d1272fc22c..36c02eb47565 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts
@@ -7,7 +7,7 @@
import { ReportingConfig, ReportingCore } from '../../../';
import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../../common/constants';
import { ConditionalHeaders } from '../../../types';
-import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only
+import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; // Logo is PDF only
export const getCustomLogo = async ({
reporting,
@@ -17,7 +17,7 @@ export const getCustomLogo = async ({
}: {
reporting: ReportingCore;
config: ReportingConfig;
- job: JobDocPayloadPDF;
+ job: ScheduledTaskParamsPDF;
conditionalHeaders: ConditionalHeaders;
}) => {
const serverBasePath: string = config.kbnConfig.get('server', 'basePath');
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts
index 5b5a6b06930f..9533c0fdc7ea 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts
@@ -5,12 +5,12 @@
*/
import { ReportingConfig } from '../../../';
-import { JobDocPayloadPNG } from '../../png/types';
-import { JobDocPayloadPDF } from '../../printable_pdf/types';
+import { ScheduledTaskParamsPNG } from '../../png/types';
+import { ScheduledTaskParamsPDF } from '../../printable_pdf/types';
import { getFullUrls } from './get_full_urls';
interface FullUrlsOpts {
- job: JobDocPayloadPNG & JobDocPayloadPDF;
+ job: ScheduledTaskParamsPNG & ScheduledTaskParamsPDF;
config: ReportingConfig;
}
@@ -35,7 +35,7 @@ beforeEach(() => {
mockConfig = getMockConfig(mockConfigGet);
});
-const getMockJob = (base: object) => base as JobDocPayloadPNG & JobDocPayloadPDF;
+const getMockJob = (base: object) => base as ScheduledTaskParamsPNG & ScheduledTaskParamsPDF;
test(`fails if no URL is passed`, async () => {
const fn = () => getFullUrls({ job: getMockJob({}), config: mockConfig } as FullUrlsOpts);
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts
index 642b1c028692..6935d9a84c00 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts
@@ -13,22 +13,26 @@ import {
import { ReportingConfig } from '../../..';
import { getAbsoluteUrlFactory } from '../../../../common/get_absolute_url';
import { validateUrls } from '../../../../common/validate_urls';
-import { JobDocPayloadPNG } from '../../png/types';
-import { JobDocPayloadPDF } from '../../printable_pdf/types';
+import { ScheduledTaskParamsPNG } from '../../png/types';
+import { ScheduledTaskParamsPDF } from '../../printable_pdf/types';
-function isPngJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPNG {
- return (job as JobDocPayloadPNG).relativeUrl !== undefined;
+function isPngJob(
+ job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF
+): job is ScheduledTaskParamsPNG {
+ return (job as ScheduledTaskParamsPNG).relativeUrl !== undefined;
}
-function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPDF {
- return (job as JobDocPayloadPDF).objects !== undefined;
+function isPdfJob(
+ job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF
+): job is ScheduledTaskParamsPDF {
+ return (job as ScheduledTaskParamsPDF).objects !== undefined;
}
-export function getFullUrls({
+export function getFullUrls({
config,
job,
}: {
config: ReportingConfig;
- job: JobDocPayloadPDF | JobDocPayloadPNG;
+ job: ScheduledTaskParamsPDF | ScheduledTaskParamsPNG;
}) {
const [basePath, protocol, hostname, port] = [
config.kbnConfig.get('server', 'basePath'),
diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts
index 5147881a980e..db7137c30513 100644
--- a/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts
@@ -9,11 +9,11 @@ import {
KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN,
} from '../../../../common/constants';
-export const omitBlacklistedHeaders = ({
+export const omitBlacklistedHeaders = ({
job,
decryptedHeaders,
}: {
- job: JobDocPayloadType;
+ job: ScheduledTaskParamsType;
decryptedHeaders: Record;
}) => {
const filteredHeaders: Record = omit(
diff --git a/x-pack/plugins/reporting/server/export_types/csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/index.ts
index 8642a6d5758a..b5eacdfc62c8 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/index.ts
@@ -15,21 +15,21 @@ import {
import { CSV_JOB_TYPE as jobType } from '../../../constants';
import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types';
import { metadata } from './metadata';
-import { createJobFactory } from './server/create_job';
-import { executeJobFactory } from './server/execute_job';
-import { JobDocPayloadDiscoverCsv, JobParamsDiscoverCsv } from './types';
+import { scheduleTaskFnFactory } from './server/create_job';
+import { runTaskFnFactory } from './server/execute_job';
+import { JobParamsDiscoverCsv, ScheduledTaskParamsCSV } from './types';
export const getExportType = (): ExportTypeDefinition<
JobParamsDiscoverCsv,
ESQueueCreateJobFn,
- JobDocPayloadDiscoverCsv,
- ESQueueWorkerExecuteFn
+ ScheduledTaskParamsCSV,
+ ESQueueWorkerExecuteFn
> => ({
...metadata,
jobType,
jobContentExtension: 'csv',
- createJobFactory,
- executeJobFactory,
+ scheduleTaskFnFactory,
+ runTaskFnFactory,
validLicenses: [
LICENSE_TYPE_TRIAL,
LICENSE_TYPE_BASIC,
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts
index acf7f0505a73..c4fa1cd8e4fa 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts
@@ -4,24 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
-import { ReportingCore } from '../../../';
import { cryptoFactory } from '../../../lib';
-import { CreateJobFactory, ESQueueCreateJobFn } from '../../../types';
+import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../types';
import { JobParamsDiscoverCsv } from '../types';
-export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) {
+>> = function createJobFactoryFn(reporting) {
const config = reporting.getConfig();
const crypto = cryptoFactory(config.get('encryptionKey'));
const setupDeps = reporting.getPluginSetupDeps();
- return async function createJob(
- jobParams: JobParamsDiscoverCsv,
- context: RequestHandlerContext,
- request: KibanaRequest
- ) {
+ return async function scheduleTask(jobParams, context, request) {
const serializedEncryptedHeaders = await crypto.encrypt(request.headers);
const savedObjectsClient = context.core.savedObjects.client;
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
index e7416b6fcf91..74703200cd00 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
@@ -20,8 +20,8 @@ import { CSV_BOM_CHARS } from '../../../../common/constants';
import { LevelLogger } from '../../../lib';
import { setFieldFormats } from '../../../services';
import { createMockReportingCore } from '../../../test_helpers';
-import { JobDocPayloadDiscoverCsv } from '../types';
-import { executeJobFactory } from './execute_job';
+import { ScheduledTaskParamsCSV } from '../types';
+import { runTaskFnFactory } from './execute_job';
const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms));
@@ -30,7 +30,7 @@ const getRandomScrollId = () => {
return puid.generate();
};
-const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadDiscoverCsv;
+const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsCSV;
describe('CSV Execute Job', function () {
const encryptionKey = 'testEncryptionKey';
@@ -125,10 +125,10 @@ describe('CSV Execute Job', function () {
describe('basic Elasticsearch call behavior', function () {
it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- await executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ await runTask(
'job456',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -145,8 +145,8 @@ describe('CSV Execute Job', function () {
testBody: true,
};
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const job = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const job = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: {
@@ -155,7 +155,7 @@ describe('CSV Execute Job', function () {
},
});
- await executeJob('job777', job, cancellationToken);
+ await runTask('job777', job, cancellationToken);
const searchCall = callAsCurrentUserStub.firstCall;
expect(searchCall.args[0]).toBe('search');
@@ -172,10 +172,10 @@ describe('CSV Execute Job', function () {
_scroll_id: scrollId,
});
callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse);
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- await executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ await runTask(
'job456',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -190,10 +190,10 @@ describe('CSV Execute Job', function () {
});
it('should not execute scroll if there are no hits from the search', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- await executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ await runTask(
'job456',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -224,10 +224,10 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- await executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ await runTask(
'job456',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -263,10 +263,10 @@ describe('CSV Execute Job', function () {
_scroll_id: lastScrollId,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- await executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ await runTask(
'job456',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -295,16 +295,16 @@ describe('CSV Execute Job', function () {
_scroll_id: lastScrollId,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: undefined,
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(`[TypeError: Cannot read property 'indexOf' of undefined]`);
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
+ `[TypeError: Cannot read property 'indexOf' of undefined]`
+ );
const lastCall = callAsCurrentUserStub.getCall(callAsCurrentUserStub.callCount - 1);
expect(lastCall.args[0]).toBe('clearScroll');
@@ -322,14 +322,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
+ const { csv_contains_formulas: csvContainsFormulas } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -347,14 +347,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['=SUM(A1:A2)', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
+ const { csv_contains_formulas: csvContainsFormulas } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -373,14 +373,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
+ const { csv_contains_formulas: csvContainsFormulas } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -399,15 +399,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['=SUM(A1:A2)', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
+ const { csv_contains_formulas: csvContainsFormulas } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -425,14 +425,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
+ const { csv_contains_formulas: csvContainsFormulas } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -452,14 +452,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toEqual(`${CSV_BOM_CHARS}one,two\none,bar\n`);
});
@@ -473,14 +473,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toEqual('one,two\none,bar\n');
});
@@ -496,14 +496,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toEqual("one,two\n\"'=cmd|' /C calc'!A0\",bar\n");
});
@@ -517,14 +517,14 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toEqual('one,two\n"=cmd|\' /C calc\'!A0",bar\n');
});
@@ -533,15 +533,15 @@ describe('CSV Execute Job', function () {
describe('Elasticsearch call errors', function () {
it('should reject Promise if search call errors out', async function () {
callAsCurrentUserStub.rejects(new Error());
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(`[Error]`);
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
+ `[Error]`
+ );
});
it('should reject Promise if scroll call errors out', async function () {
@@ -552,15 +552,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
callAsCurrentUserStub.onSecondCall().rejects(new Error());
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(`[Error]`);
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
+ `[Error]`
+ );
});
});
@@ -573,15 +573,13 @@ describe('CSV Execute Job', function () {
_scroll_id: undefined,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
`[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[{}]}}]`
);
});
@@ -594,15 +592,13 @@ describe('CSV Execute Job', function () {
_scroll_id: undefined,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
`[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[]}}]`
);
});
@@ -622,15 +618,13 @@ describe('CSV Execute Job', function () {
_scroll_id: undefined,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
`[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[{}]}}]`
);
});
@@ -650,15 +644,13 @@ describe('CSV Execute Job', function () {
_scroll_id: undefined,
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
});
- await expect(
- executeJob('job123', jobParams, cancellationToken)
- ).rejects.toMatchInlineSnapshot(
+ await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot(
`[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[]}}]`
);
});
@@ -687,10 +679,10 @@ describe('CSV Execute Job', function () {
});
it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ runTask(
'job345',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -706,10 +698,10 @@ describe('CSV Execute Job', function () {
});
it(`shouldn't call clearScroll if it never got a scrollId`, async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ runTask(
'job345',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -724,10 +716,10 @@ describe('CSV Execute Job', function () {
});
it('should call clearScroll if it got a scrollId', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- executeJob(
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ runTask(
'job345',
- getJobDocPayload({
+ getScheduledTaskParams({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
@@ -746,54 +738,54 @@ describe('CSV Execute Job', function () {
describe('csv content', function () {
it('should write column headers to output, even if there are no results', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toBe(`one,two\n`);
});
it('should use custom uiSettings csv:separator for header', async function () {
mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(';');
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toBe(`one;two\n`);
});
it('should escape column headers if uiSettings csv:quoteValues is true', async function () {
mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true);
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one and a half', 'two', 'three-and-four', 'five & six'],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toBe(`"one and a half",two,"three-and-four","five & six"\n`);
});
it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () {
mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(false);
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one and a half', 'two', 'three-and-four', 'five & six'],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
expect(content).toBe(`one and a half,two,three-and-four,five & six\n`);
});
it('should write column headers to output, when there are results', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
callAsCurrentUserStub.onFirstCall().resolves({
hits: {
hits: [{ one: '1', two: '2' }],
@@ -801,19 +793,19 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const jobParams = getJobDocPayload({
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
const lines = content.split('\n');
const headerLine = lines[0];
expect(headerLine).toBe('one,two');
});
it('should use comma separated values of non-nested fields from _source', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
callAsCurrentUserStub.onFirstCall().resolves({
hits: {
hits: [{ _source: { one: 'foo', two: 'bar' } }],
@@ -821,20 +813,20 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const jobParams = getJobDocPayload({
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
const lines = content.split('\n');
const valuesLine = lines[1];
expect(valuesLine).toBe('foo,bar');
});
it('should concatenate the hits from multiple responses', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
callAsCurrentUserStub.onFirstCall().resolves({
hits: {
hits: [{ _source: { one: 'foo', two: 'bar' } }],
@@ -848,13 +840,13 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const jobParams = getJobDocPayload({
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
const lines = content.split('\n');
expect(lines[1]).toBe('foo,bar');
@@ -862,7 +854,7 @@ describe('CSV Execute Job', function () {
});
it('should use field formatters to format fields', async function () {
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
callAsCurrentUserStub.onFirstCall().resolves({
hits: {
hits: [{ _source: { one: 'foo', two: 'bar' } }],
@@ -870,7 +862,7 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const jobParams = getJobDocPayload({
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
@@ -885,7 +877,7 @@ describe('CSV Execute Job', function () {
},
},
});
- const { content } = await executeJob('job123', jobParams, cancellationToken);
+ const { content } = await runTask('job123', jobParams, cancellationToken);
const lines = content.split('\n');
expect(lines[1]).toBe('FOO,bar');
@@ -904,14 +896,14 @@ describe('CSV Execute Job', function () {
beforeEach(async function () {
configGetStub.withArgs('csv', 'maxSizeBytes').returns(1);
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
});
- ({ content, max_size_reached: maxSizeReached } = await executeJob(
+ ({ content, max_size_reached: maxSizeReached } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -934,14 +926,14 @@ describe('CSV Execute Job', function () {
beforeEach(async function () {
configGetStub.withArgs('csv', 'maxSizeBytes').returns(9);
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
});
- ({ content, max_size_reached: maxSizeReached } = await executeJob(
+ ({ content, max_size_reached: maxSizeReached } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -971,15 +963,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- ({ content, max_size_reached: maxSizeReached } = await executeJob(
+ ({ content, max_size_reached: maxSizeReached } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -1011,15 +1003,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- ({ content, max_size_reached: maxSizeReached } = await executeJob(
+ ({ content, max_size_reached: maxSizeReached } = await runTask(
'job123',
jobParams,
cancellationToken
@@ -1048,15 +1040,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- await executeJob('job123', jobParams, cancellationToken);
+ await runTask('job123', jobParams, cancellationToken);
const searchCall = callAsCurrentUserStub.firstCall;
expect(searchCall.args[0]).toBe('search');
@@ -1074,15 +1066,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- await executeJob('job123', jobParams, cancellationToken);
+ await runTask('job123', jobParams, cancellationToken);
const searchCall = callAsCurrentUserStub.firstCall;
expect(searchCall.args[0]).toBe('search');
@@ -1100,15 +1092,15 @@ describe('CSV Execute Job', function () {
_scroll_id: 'scrollId',
});
- const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
- const jobParams = getJobDocPayload({
+ const runTask = await runTaskFnFactory(mockReportingCore, mockLogger);
+ const jobParams = getScheduledTaskParams({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
});
- await executeJob('job123', jobParams, cancellationToken);
+ await runTask('job123', jobParams, cancellationToken);
const scrollCall = callAsCurrentUserStub.secondCall;
expect(scrollCall.args[0]).toBe('scroll');
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
index 91a4db0469fb..89fd014502f7 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
@@ -8,31 +8,26 @@ import { i18n } from '@kbn/i18n';
import Hapi from 'hapi';
import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server';
import {
- CSV_SEPARATOR_SETTING,
CSV_QUOTE_VALUES_SETTING,
+ CSV_SEPARATOR_SETTING,
} from '../../../../../../../src/plugins/share/server';
-import { ReportingCore } from '../../..';
import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../../common/constants';
import { getFieldFormats } from '../../../../server/services';
-import { cryptoFactory, LevelLogger } from '../../../lib';
-import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../types';
-import { JobDocPayloadDiscoverCsv } from '../types';
+import { cryptoFactory } from '../../../lib';
+import { ESQueueWorkerExecuteFn, RunTaskFnFactory } from '../../../types';
+import { ScheduledTaskParamsCSV } from '../types';
import { fieldFormatMapFactory } from './lib/field_format_map';
import { createGenerateCsv } from './lib/generate_csv';
-export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) {
+export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) {
const config = reporting.getConfig();
const crypto = cryptoFactory(config.get('encryptionKey'));
const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']);
const serverBasePath = config.kbnConfig.get('server', 'basePath');
- return async function executeJob(
- jobId: string,
- job: JobDocPayloadDiscoverCsv,
- cancellationToken: any
- ) {
+ return async function runTask(jobId, job, cancellationToken) {
const elasticsearch = reporting.getElasticsearchService();
const jobLogger = logger.clone([jobId]);
diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts
index c80cd5fd24fe..ab3e114c7c99 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts
@@ -5,7 +5,7 @@
*/
import { CancellationToken } from '../../../common';
-import { JobParamPostPayload, JobDocPayload, ScrollConfig } from '../../types';
+import { JobParamPostPayload, ScheduledTaskParams, ScrollConfig } from '../../types';
export type RawValue = string | object | null | undefined;
@@ -32,7 +32,7 @@ export interface JobParamsDiscoverCsv {
post?: JobParamPostPayloadDiscoverCsv;
}
-export interface JobDocPayloadDiscoverCsv extends JobDocPayload {
+export interface ScheduledTaskParamsCSV extends ScheduledTaskParams {
basePath: string;
searchRequest: any;
fields: any;
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts
index 65802ee5bb7f..961a046c846e 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts
@@ -15,16 +15,16 @@ import {
import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../constants';
import { ExportTypeDefinition } from '../../types';
import { metadata } from './metadata';
-import { createJobFactory, ImmediateCreateJobFn } from './server/create_job';
-import { executeJobFactory, ImmediateExecuteFn } from './server/execute_job';
+import { ImmediateCreateJobFn, scheduleTaskFnFactory } from './server/create_job';
+import { ImmediateExecuteFn, runTaskFnFactory } from './server/execute_job';
import { JobParamsPanelCsv } from './types';
/*
* These functions are exported to share with the API route handler that
* generates csv from saved object immediately on request.
*/
-export { createJobFactory } from './server/create_job';
-export { executeJobFactory } from './server/execute_job';
+export { scheduleTaskFnFactory } from './server/create_job';
+export { runTaskFnFactory } from './server/execute_job';
export const getExportType = (): ExportTypeDefinition<
JobParamsPanelCsv,
@@ -35,8 +35,8 @@ export const getExportType = (): ExportTypeDefinition<
...metadata,
jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE,
jobContentExtension: 'csv',
- createJobFactory,
- executeJobFactory,
+ scheduleTaskFnFactory,
+ runTaskFnFactory,
validLicenses: [
LICENSE_TYPE_TRIAL,
LICENSE_TYPE_BASIC,
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts
deleted file mode 100644
index c187da5104d3..000000000000
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts
+++ /dev/null
@@ -1,106 +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 { notFound, notImplemented } from 'boom';
-import { get } from 'lodash';
-import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
-import { ReportingCore } from '../../../..';
-import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../../common/constants';
-import { cryptoFactory, LevelLogger } from '../../../../lib';
-import { CreateJobFactory, TimeRangeParams } from '../../../../types';
-import {
- JobDocPayloadPanelCsv,
- JobParamsPanelCsv,
- SavedObject,
- SavedObjectServiceError,
- SavedSearchObjectAttributesJSON,
- SearchPanel,
- VisObjectAttributesJSON,
-} from '../../types';
-import { createJobSearch } from './create_job_search';
-
-export type ImmediateCreateJobFn = (
- jobParams: JobParamsType,
- headers: KibanaRequest['headers'],
- context: RequestHandlerContext,
- req: KibanaRequest
-) => Promise<{
- type: string | null;
- title: string;
- jobParams: JobParamsType;
-}>;
-
-interface VisData {
- title: string;
- visType: string;
- panel: SearchPanel;
-}
-
-export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) {
- const config = reporting.getConfig();
- const crypto = cryptoFactory(config.get('encryptionKey'));
- const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']);
-
- return async function createJob(
- jobParams: JobParamsPanelCsv,
- headers: KibanaRequest['headers'],
- context: RequestHandlerContext,
- req: KibanaRequest
- ): Promise {
- const { savedObjectType, savedObjectId } = jobParams;
- const serializedEncryptedHeaders = await crypto.encrypt(headers);
-
- const { panel, title, visType }: VisData = await Promise.resolve()
- .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId))
- .then(async (savedObject: SavedObject) => {
- const { attributes, references } = savedObject;
- const {
- kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON,
- } = attributes as SavedSearchObjectAttributesJSON;
- const { timerange } = req.body as { timerange: TimeRangeParams };
-
- if (!kibanaSavedObjectMetaJSON) {
- throw new Error('Could not parse saved object data!');
- }
-
- const kibanaSavedObjectMeta = {
- ...kibanaSavedObjectMetaJSON,
- searchSource: JSON.parse(kibanaSavedObjectMetaJSON.searchSourceJSON),
- };
-
- const { visState: visStateJSON } = attributes as VisObjectAttributesJSON;
- if (visStateJSON) {
- throw notImplemented('Visualization types are not yet implemented');
- }
-
- // saved search type
- return await createJobSearch(timerange, attributes, references, kibanaSavedObjectMeta);
- })
- .catch((err: Error) => {
- const boomErr = (err as unknown) as { isBoom: boolean };
- if (boomErr.isBoom) {
- throw err;
- }
- const errPayload: SavedObjectServiceError = get(err, 'output.payload', { statusCode: 0 });
- if (errPayload.statusCode === 404) {
- throw notFound(errPayload.message);
- }
- if (err.stack) {
- logger.error(err.stack);
- }
- throw new Error(`Unable to create a job from saved object data! Error: ${err}`);
- });
-
- return {
- headers: serializedEncryptedHeaders,
- jobParams: { ...jobParams, panel, visType },
- type: null,
- title,
- };
- };
-};
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts
index a3674d69ae6a..dafac0401760 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts
@@ -4,4 +4,96 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { createJobFactory, ImmediateCreateJobFn } from './create_job';
+import { notFound, notImplemented } from 'boom';
+import { get } from 'lodash';
+import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
+import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../../common/constants';
+import { cryptoFactory } from '../../../../lib';
+import { ScheduleTaskFnFactory, TimeRangeParams } from '../../../../types';
+import {
+ JobParamsPanelCsv,
+ SavedObject,
+ SavedObjectServiceError,
+ SavedSearchObjectAttributesJSON,
+ SearchPanel,
+ VisObjectAttributesJSON,
+} from '../../types';
+import { createJobSearch } from './create_job_search';
+
+export type ImmediateCreateJobFn = (
+ jobParams: JobParamsType,
+ headers: KibanaRequest['headers'],
+ context: RequestHandlerContext,
+ req: KibanaRequest
+) => Promise<{
+ type: string | null;
+ title: string;
+ jobParams: JobParamsType;
+}>;
+
+interface VisData {
+ title: string;
+ visType: string;
+ panel: SearchPanel;
+}
+
+export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting, parentLogger) {
+ const config = reporting.getConfig();
+ const crypto = cryptoFactory(config.get('encryptionKey'));
+ const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']);
+
+ return async function scheduleTask(jobParams, headers, context, req) {
+ const { savedObjectType, savedObjectId } = jobParams;
+ const serializedEncryptedHeaders = await crypto.encrypt(headers);
+
+ const { panel, title, visType }: VisData = await Promise.resolve()
+ .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId))
+ .then(async (savedObject: SavedObject) => {
+ const { attributes, references } = savedObject;
+ const {
+ kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON,
+ } = attributes as SavedSearchObjectAttributesJSON;
+ const { timerange } = req.body as { timerange: TimeRangeParams };
+
+ if (!kibanaSavedObjectMetaJSON) {
+ throw new Error('Could not parse saved object data!');
+ }
+
+ const kibanaSavedObjectMeta = {
+ ...kibanaSavedObjectMetaJSON,
+ searchSource: JSON.parse(kibanaSavedObjectMetaJSON.searchSourceJSON),
+ };
+
+ const { visState: visStateJSON } = attributes as VisObjectAttributesJSON;
+ if (visStateJSON) {
+ throw notImplemented('Visualization types are not yet implemented');
+ }
+
+ // saved search type
+ return await createJobSearch(timerange, attributes, references, kibanaSavedObjectMeta);
+ })
+ .catch((err: Error) => {
+ const boomErr = (err as unknown) as { isBoom: boolean };
+ if (boomErr.isBoom) {
+ throw err;
+ }
+ const errPayload: SavedObjectServiceError = get(err, 'output.payload', { statusCode: 0 });
+ if (errPayload.statusCode === 404) {
+ throw notFound(errPayload.message);
+ }
+ if (err.stack) {
+ logger.error(err.stack);
+ }
+ throw new Error(`Unable to create a job from saved object data! Error: ${err}`);
+ });
+
+ return {
+ headers: serializedEncryptedHeaders,
+ jobParams: { ...jobParams, panel, visType },
+ type: null,
+ title,
+ };
+ };
+};
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts
index d555100b6320..26b7a24907f4 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts
@@ -6,39 +6,33 @@
import { i18n } from '@kbn/i18n';
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
-import { ReportingCore } from '../../..';
import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants';
-import { cryptoFactory, LevelLogger } from '../../../lib';
-import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../types';
+import { cryptoFactory } from '../../../lib';
+import { RunTaskFnFactory, ScheduledTaskParams, TaskRunResult } from '../../../types';
import { CsvResultFromSearch } from '../../csv/types';
-import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types';
+import { FakeRequest, JobParamsPanelCsv, SearchPanel } from '../types';
import { createGenerateCsv } from './lib';
/*
* ImmediateExecuteFn receives the job doc payload because the payload was
- * generated in the CreateFn
+ * generated in the ScheduleFn
*/
export type ImmediateExecuteFn = (
jobId: null,
- job: JobDocPayload,
+ job: ScheduledTaskParams,
context: RequestHandlerContext,
req: KibanaRequest
-) => Promise;
+) => Promise;
-export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) {
+>> = function executeJobFactoryFn(reporting, parentLogger) {
const config = reporting.getConfig();
const crypto = cryptoFactory(config.get('encryptionKey'));
const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']);
const generateCsv = createGenerateCsv(reporting, parentLogger);
- return async function executeJob(
- jobId: string | null,
- job: JobDocPayloadPanelCsv,
- context,
- req
- ): Promise {
+ return async function runTask(jobId: string | null, job, context, req) {
// There will not be a jobID for "immediate" generation.
// jobID is only for "queued" jobs
// Use the jobID as a logging tag or "immediate"
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts
index 36ae5b1dac05..835b352953df 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { JobParamPostPayload, JobDocPayload, TimeRangeParams } from '../../types';
+import { JobParamPostPayload, ScheduledTaskParams, TimeRangeParams } from '../../types';
export interface FakeRequest {
headers: Record;
@@ -23,7 +23,7 @@ export interface JobParamsPanelCsv {
visType?: string;
}
-export interface JobDocPayloadPanelCsv extends JobDocPayload {
+export interface ScheduledTaskParamsPanelCsv extends ScheduledTaskParams {
jobParams: JobParamsPanelCsv;
}
diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts
index a3b51e365e77..b708448b0f8b 100644
--- a/x-pack/plugins/reporting/server/export_types/png/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/index.ts
@@ -14,22 +14,22 @@ import {
} from '../../../common/constants';
import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../..//types';
import { metadata } from './metadata';
-import { createJobFactory } from './server/create_job';
-import { executeJobFactory } from './server/execute_job';
-import { JobDocPayloadPNG, JobParamsPNG } from './types';
+import { scheduleTaskFnFactory } from './server/create_job';
+import { runTaskFnFactory } from './server/execute_job';
+import { JobParamsPNG, ScheduledTaskParamsPNG } from './types';
export const getExportType = (): ExportTypeDefinition<
JobParamsPNG,
ESQueueCreateJobFn,
- JobDocPayloadPNG,
- ESQueueWorkerExecuteFn
+ ScheduledTaskParamsPNG,
+ ESQueueWorkerExecuteFn
> => ({
...metadata,
jobType,
jobContentEncoding: 'base64',
jobContentExtension: 'PNG',
- createJobFactory,
- executeJobFactory,
+ scheduleTaskFnFactory,
+ runTaskFnFactory,
validLicenses: [
LICENSE_TYPE_TRIAL,
LICENSE_TYPE_STANDARD,
diff --git a/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts
index 3f1556fb2978..f459b8f249c7 100644
--- a/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts
@@ -6,17 +6,17 @@
import { validateUrls } from '../../../../../common/validate_urls';
import { cryptoFactory } from '../../../../lib';
-import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types';
+import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../../types';
import { JobParamsPNG } from '../../types';
-export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting) {
const config = reporting.getConfig();
const setupDeps = reporting.getPluginSetupDeps();
const crypto = cryptoFactory(config.get('encryptionKey'));
- return async function createJob(
+ return async function scheduleTask(
{ objectType, title, relativeUrl, browserTimezone, layout },
context,
req
diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts
index b92f53ff6563..3d3f156aeef0 100644
--- a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts
@@ -9,9 +9,9 @@ import { ReportingCore } from '../../../../';
import { CancellationToken } from '../../../../../common';
import { cryptoFactory, LevelLogger } from '../../../../lib';
import { createMockReportingCore } from '../../../../test_helpers';
-import { JobDocPayloadPNG } from '../../types';
+import { ScheduledTaskParamsPNG } from '../../types';
import { generatePngObservableFactory } from '../lib/generate_png';
-import { executeJobFactory } from './';
+import { runTaskFnFactory } from './';
jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() }));
@@ -36,7 +36,7 @@ const encryptHeaders = async (headers: Record) => {
return await crypto.encrypt(headers);
};
-const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPNG;
+const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPNG;
beforeEach(async () => {
const kbnConfig = {
@@ -81,11 +81,11 @@ test(`passes browserTimezone to generatePng`, async () => {
const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
generatePngObservable.mockReturnValue(Rx.of(Buffer.from('')));
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
- await executeJob(
+ await runTask(
'pngJobId',
- getJobDocPayload({
+ getScheduledTaskParams({
relativeUrl: '/app/kibana#/something',
browserTimezone,
headers: encryptedHeaders,
@@ -125,15 +125,15 @@ test(`passes browserTimezone to generatePng`, async () => {
});
test(`returns content_type of application/png`, async () => {
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of('foo'));
- const { content_type: contentType } = await executeJob(
+ const { content_type: contentType } = await runTask(
'pngJobId',
- getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
+ getScheduledTaskParams({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
cancellationToken
);
expect(contentType).toBe('image/png');
@@ -144,11 +144,11 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => {
const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent }));
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
- const { content } = await executeJob(
+ const { content } = await runTask(
'pngJobId',
- getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
+ getScheduledTaskParams({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
cancellationToken
);
diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts
index ea4c4b1d106a..c9ab890dc8a5 100644
--- a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts
@@ -7,37 +7,35 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators';
-import { ReportingCore } from '../../../..';
import { PNG_JOB_TYPE } from '../../../../../common/constants';
-import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../..//types';
-import { LevelLogger } from '../../../../lib';
+import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../..//types';
import {
decryptJobHeaders,
getConditionalHeaders,
getFullUrls,
omitBlacklistedHeaders,
} from '../../../common/execute_job/';
-import { JobDocPayloadPNG } from '../../types';
+import { ScheduledTaskParamsPNG } from '../../types';
import { generatePngObservableFactory } from '../lib/generate_png';
-type QueuedPngExecutorFactory = ExecuteJobFactory>;
+type QueuedPngExecutorFactory = RunTaskFnFactory>;
-export const executeJobFactory: QueuedPngExecutorFactory = async function executeJobFactoryFn(
- reporting: ReportingCore,
- parentLogger: LevelLogger
+export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn(
+ reporting,
+ parentLogger
) {
const config = reporting.getConfig();
const encryptionKey = config.get('encryptionKey');
const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']);
- return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) {
+ return async function runTask(jobId, job, cancellationToken) {
const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting');
const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup');
let apmGeneratePng: { end: () => void } | null | undefined;
const generatePngObservable = await generatePngObservableFactory(reporting);
const jobLogger = logger.clone([jobId]);
- const process$: Rx.Observable = Rx.of(1).pipe(
+ const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })),
map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })),
map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })),
diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts
index 486a8e91a722..7a25f4ed8fe7 100644
--- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { JobDocPayload } from '../../../server/types';
+import { ScheduledTaskParams } from '../../../server/types';
import { LayoutInstance, LayoutParams } from '../common/layouts';
// Job params: structure of incoming user request data
@@ -17,7 +17,7 @@ export interface JobParamsPNG {
}
// Job payload: structure of stored job data provided by create_job
-export interface JobDocPayloadPNG extends JobDocPayload {
+export interface ScheduledTaskParamsPNG extends ScheduledTaskParams {
basePath?: string;
browserTimezone: string;
forceNow?: string;
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts
index 39a0cbd5270a..073bd38b538f 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts
@@ -14,22 +14,22 @@ import {
} from '../../../common/constants';
import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types';
import { metadata } from './metadata';
-import { createJobFactory } from './server/create_job';
-import { executeJobFactory } from './server/execute_job';
-import { JobDocPayloadPDF, JobParamsPDF } from './types';
+import { scheduleTaskFnFactory } from './server/create_job';
+import { runTaskFnFactory } from './server/execute_job';
+import { JobParamsPDF, ScheduledTaskParamsPDF } from './types';
export const getExportType = (): ExportTypeDefinition<
JobParamsPDF,
ESQueueCreateJobFn,
- JobDocPayloadPDF,
- ESQueueWorkerExecuteFn
+ ScheduledTaskParamsPDF,
+ ESQueueWorkerExecuteFn
> => ({
...metadata,
jobType,
jobContentEncoding: 'base64',
jobContentExtension: 'pdf',
- createJobFactory,
- executeJobFactory,
+ scheduleTaskFnFactory,
+ runTaskFnFactory,
validLicenses: [
LICENSE_TYPE_TRIAL,
LICENSE_TYPE_STANDARD,
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts
index 44dc5dc662d5..5549209b5ac0 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts
@@ -7,12 +7,12 @@
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
import { validateUrls } from '../../../../../common/validate_urls';
import { cryptoFactory } from '../../../../lib';
-import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types';
+import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../../types';
import { JobParamsPDF } from '../../types';
// @ts-ignore no module def (deprecated module)
import { compatibilityShimFactory } from './compatibility_shim';
-export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting, logger) {
const config = reporting.getConfig();
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts
index 2693e16e41f1..834d325058c7 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts
@@ -11,9 +11,9 @@ import { ReportingCore } from '../../../../';
import { CancellationToken } from '../../../../../common';
import { cryptoFactory, LevelLogger } from '../../../../lib';
import { createMockReportingCore } from '../../../../test_helpers';
-import { JobDocPayloadPDF } from '../../types';
+import { ScheduledTaskParamsPDF } from '../../types';
import { generatePdfObservableFactory } from '../lib/generate_pdf';
-import { executeJobFactory } from './';
+import { runTaskFnFactory } from './';
let mockReporting: ReportingCore;
@@ -36,7 +36,7 @@ const encryptHeaders = async (headers: Record) => {
return await crypto.encrypt(headers);
};
-const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPDF;
+const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPDF;
beforeEach(async () => {
const kbnConfig = {
@@ -79,11 +79,11 @@ test(`passes browserTimezone to generatePdf`, async () => {
const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock;
generatePdfObservable.mockReturnValue(Rx.of(Buffer.from('')));
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
- await executeJob(
+ await runTask(
'pdfJobId',
- getJobDocPayload({
+ getScheduledTaskParams({
title: 'PDF Params Timezone Test',
relativeUrl: '/app/kibana#/something',
browserTimezone,
@@ -98,15 +98,15 @@ test(`passes browserTimezone to generatePdf`, async () => {
test(`returns content_type of application/pdf`, async () => {
const logger = getMockLogger();
- const executeJob = await executeJobFactory(mockReporting, logger);
+ const runTask = await runTaskFnFactory(mockReporting, logger);
const encryptedHeaders = await encryptHeaders({});
const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
- const { content_type: contentType } = await executeJob(
+ const { content_type: contentType } = await runTask(
'pdfJobId',
- getJobDocPayload({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only
+ getScheduledTaskParams({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only
cancellationToken
);
expect(contentType).toBe('application/pdf');
@@ -117,11 +117,11 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => {
const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
- const { content } = await executeJob(
+ const { content } = await runTask(
'pdfJobId',
- getJobDocPayload({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only
+ getScheduledTaskParams({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only
cancellationToken
);
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts
index a4d84b2f9f1e..7f8f2f4f6906 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts
@@ -7,10 +7,8 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators';
-import { ReportingCore } from '../../../..';
import { PDF_JOB_TYPE } from '../../../../../common/constants';
-import { LevelLogger } from '../../../../lib';
-import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../types';
+import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../../types';
import {
decryptJobHeaders,
getConditionalHeaders,
@@ -18,21 +16,21 @@ import {
getFullUrls,
omitBlacklistedHeaders,
} from '../../../common/execute_job';
-import { JobDocPayloadPDF } from '../../types';
+import { ScheduledTaskParamsPDF } from '../../types';
import { generatePdfObservableFactory } from '../lib/generate_pdf';
-type QueuedPdfExecutorFactory = ExecuteJobFactory>;
+type QueuedPdfExecutorFactory = RunTaskFnFactory>;
-export const executeJobFactory: QueuedPdfExecutorFactory = async function executeJobFactoryFn(
- reporting: ReportingCore,
- parentLogger: LevelLogger
+export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn(
+ reporting,
+ parentLogger
) {
const config = reporting.getConfig();
const encryptionKey = config.get('encryptionKey');
const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']);
- return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) {
+ return async function runTask(jobId, job, cancellationToken) {
const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting');
const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup');
let apmGeneratePdf: { end: () => void } | null | undefined;
@@ -40,7 +38,7 @@ export const executeJobFactory: QueuedPdfExecutorFactory = async function execut
const generatePdfObservable = await generatePdfObservableFactory(reporting);
const jobLogger = logger.clone([jobId]);
- const process$: Rx.Observable = Rx.of(1).pipe(
+ const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })),
map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })),
map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })),
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts
index 792b6734b9ec..e32ef565e8c0 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { JobDocPayload } from '../../../server/types';
+import { ScheduledTaskParams } from '../../../server/types';
import { LayoutInstance, LayoutParams } from '../common/layouts';
// Job params: structure of incoming user request data, after being parsed from RISON
@@ -17,7 +17,7 @@ export interface JobParamsPDF {
}
// Job payload: structure of stored job data provided by create_job
-export interface JobDocPayloadPDF extends JobDocPayload {
+export interface ScheduledTaskParamsPDF extends ScheduledTaskParams {
basePath?: string;
browserTimezone: string;
forceNow?: string;
diff --git a/x-pack/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts
index d993a17c0b31..5d09af312a41 100644
--- a/x-pack/plugins/reporting/server/lib/create_queue.ts
+++ b/x-pack/plugins/reporting/server/lib/create_queue.ts
@@ -5,7 +5,7 @@
*/
import { ReportingCore } from '../core';
-import { JobDocOutput, JobSource } from '../types';
+import { JobSource, TaskRunResult } from '../types';
import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed
import { createWorkerFactory } from './create_worker';
import { Job } from './enqueue_job';
@@ -31,11 +31,11 @@ export interface ESQueueInstance {
) => ESQueueWorker;
}
-// GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn,
+// GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn,
type GenericWorkerFn = (
jobSource: JobSource,
...workerRestArgs: any[]
-) => void | Promise;
+) => void | Promise;
export async function createQueueFactory(
reporting: ReportingCore,
diff --git a/x-pack/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/plugins/reporting/server/lib/create_worker.test.ts
index 8e1174e01aa7..85188c07eeb2 100644
--- a/x-pack/plugins/reporting/server/lib/create_worker.test.ts
+++ b/x-pack/plugins/reporting/server/lib/create_worker.test.ts
@@ -26,7 +26,7 @@ const executeJobFactoryStub = sinon.stub();
const getMockLogger = sinon.stub();
const getMockExportTypesRegistry = (
- exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }]
+ exportTypes: any[] = [{ runTaskFnFactory: executeJobFactoryStub }]
) =>
({
getAll: () => exportTypes,
@@ -75,11 +75,11 @@ Object {
test('Creates a single Esqueue worker for Reporting, even if there are multiple export types', async () => {
const exportTypesRegistry = getMockExportTypesRegistry([
- { executeJobFactory: executeJobFactoryStub },
- { executeJobFactory: executeJobFactoryStub },
- { executeJobFactory: executeJobFactoryStub },
- { executeJobFactory: executeJobFactoryStub },
- { executeJobFactory: executeJobFactoryStub },
+ { runTaskFnFactory: executeJobFactoryStub },
+ { runTaskFnFactory: executeJobFactoryStub },
+ { runTaskFnFactory: executeJobFactoryStub },
+ { runTaskFnFactory: executeJobFactoryStub },
+ { runTaskFnFactory: executeJobFactoryStub },
]);
mockReporting.getExportTypesRegistry = () => exportTypesRegistry;
const createWorker = createWorkerFactory(mockReporting, getMockLogger());
diff --git a/x-pack/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts
index c9e865668bb3..837be1f44a09 100644
--- a/x-pack/plugins/reporting/server/lib/create_worker.ts
+++ b/x-pack/plugins/reporting/server/lib/create_worker.ts
@@ -27,7 +27,7 @@ export function createWorkerFactory(reporting: ReportingCore, log
for (const exportType of reporting.getExportTypesRegistry().getAll() as Array<
ExportTypeDefinition>
>) {
- const jobExecutor = await exportType.executeJobFactory(reporting, logger); // FIXME: does not "need" to be async
+ const jobExecutor = exportType.runTaskFnFactory(reporting, logger);
jobExecutors.set(exportType.jobType, jobExecutor);
}
diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts
index 3837f593df5b..625da90f3b4f 100644
--- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts
+++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts
@@ -52,7 +52,7 @@ export function enqueueJobFactory(
context: RequestHandlerContext,
request: KibanaRequest
): Promise {
- type CreateJobFn = ESQueueCreateJobFn;
+ type ScheduleTaskFnType = ESQueueCreateJobFn;
const username = user ? user.username : false;
const esqueue = await reporting.getEsqueue();
const exportType = reporting.getExportTypesRegistry().getById(exportTypeId);
@@ -61,8 +61,8 @@ export function enqueueJobFactory(
throw new Error(`Export type ${exportTypeId} does not exist in the registry!`);
}
- const createJob = exportType.createJobFactory(reporting, logger) as CreateJobFn;
- const payload = await createJob(jobParams, context, request);
+ const scheduleTask = exportType.scheduleTaskFnFactory(reporting, logger) as ScheduleTaskFnType;
+ const payload = await scheduleTask(jobParams, context, request);
const options = {
timeout: queueTimeout,
diff --git a/x-pack/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/plugins/reporting/server/lib/export_types_registry.ts
index 893a2635561f..501989f21103 100644
--- a/x-pack/plugins/reporting/server/lib/export_types_registry.ts
+++ b/x-pack/plugins/reporting/server/lib/export_types_registry.ts
@@ -5,15 +5,14 @@
*/
import { isString } from 'lodash';
-import memoizeOne from 'memoize-one';
import { getExportType as getTypeCsv } from '../export_types/csv';
import { getExportType as getTypeCsvFromSavedObject } from '../export_types/csv_from_savedobject';
import { getExportType as getTypePng } from '../export_types/png';
import { getExportType as getTypePrintablePdf } from '../export_types/printable_pdf';
import { ExportTypeDefinition } from '../types';
-type GetCallbackFn = (
- item: ExportTypeDefinition
+type GetCallbackFn = (
+ item: ExportTypeDefinition
) => boolean;
// => ExportTypeDefinition
@@ -22,8 +21,8 @@ export class ExportTypesRegistry {
constructor() {}
- register(
- item: ExportTypeDefinition
+ register(
+ item: ExportTypeDefinition
): void {
if (!isString(item.id)) {
throw new Error(`'item' must have a String 'id' property `);
@@ -33,8 +32,6 @@ export class ExportTypesRegistry {
throw new Error(`'item' with id ${item.id} has already been registered`);
}
- // TODO: Unwrap the execute function from the item's executeJobFactory
- // Move that work out of server/lib/create_worker to reduce dependence on ESQueue
this._map.set(item.id, item);
}
@@ -46,24 +43,24 @@ export class ExportTypesRegistry {
return this._map.size;
}
- getById(
+ getById(
id: string
- ): ExportTypeDefinition {
+ ): ExportTypeDefinition {
if (!this._map.has(id)) {
throw new Error(`Unknown id ${id}`);
}
return this._map.get(id) as ExportTypeDefinition<
JobParamsType,
- CreateJobFnType,
+ ScheduleTaskFnType,
JobPayloadType,
- ExecuteJobFnType
+ RunTaskFnType
>;
}
- get(
- findType: GetCallbackFn
- ): ExportTypeDefinition {
+ get(
+ findType: GetCallbackFn
+ ): ExportTypeDefinition {
let result;
for (const value of this._map.values()) {
if (!findType(value)) {
@@ -71,9 +68,9 @@ export class ExportTypesRegistry {
}
const foundResult: ExportTypeDefinition<
JobParamsType,
- CreateJobFnType,
+ ScheduleTaskFnType,
JobPayloadType,
- ExecuteJobFnType
+ RunTaskFnType
> = value;
if (result) {
@@ -91,7 +88,7 @@ export class ExportTypesRegistry {
}
}
-function getExportTypesRegistryFn(): ExportTypesRegistry {
+export function getExportTypesRegistry(): ExportTypesRegistry {
const registry = new ExportTypesRegistry();
/* this replaces the previously async method of registering export types,
@@ -108,6 +105,3 @@ function getExportTypesRegistryFn(): ExportTypesRegistry {
});
return registry;
}
-
-// FIXME: is this the best way to return a singleton?
-export const getExportTypesRegistry = memoizeOne(getExportTypesRegistryFn);
diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts
index 1221f6785541..7d93a36c85bc 100644
--- a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts
+++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts
@@ -7,12 +7,12 @@
import { schema } from '@kbn/config-schema';
import { ReportingCore } from '../';
import { API_BASE_GENERATE_V1 } from '../../common/constants';
-import { createJobFactory } from '../export_types/csv_from_savedobject/server/create_job';
-import { executeJobFactory } from '../export_types/csv_from_savedobject/server/execute_job';
+import { scheduleTaskFnFactory } from '../export_types/csv_from_savedobject/server/create_job';
+import { runTaskFnFactory } from '../export_types/csv_from_savedobject/server/execute_job';
import { getJobParamsFromRequest } from '../export_types/csv_from_savedobject/server/lib/get_job_params_from_request';
-import { JobDocPayloadPanelCsv } from '../export_types/csv_from_savedobject/types';
+import { ScheduledTaskParamsPanelCsv } from '../export_types/csv_from_savedobject/types';
import { LevelLogger as Logger } from '../lib';
-import { JobDocOutput } from '../types';
+import { TaskRunResult } from '../types';
import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing';
import { HandlerErrorFunction } from './types';
@@ -36,8 +36,8 @@ export function registerGenerateCsvFromSavedObjectImmediate(
/*
* CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does:
- * - re-use the createJob function to build up es query config
- * - re-use the executeJob function to run the scan and scroll queries and capture the entire CSV in a result object.
+ * - re-use the scheduleTask function to build up es query config
+ * - re-use the runTask function to run the scan and scroll queries and capture the entire CSV in a result object.
*/
router.post(
{
@@ -60,11 +60,11 @@ export function registerGenerateCsvFromSavedObjectImmediate(
userHandler(async (user, context, req, res) => {
const logger = parentLogger.clone(['savedobject-csv']);
const jobParams = getJobParamsFromRequest(req, { isImmediate: true });
- const createJobFn = createJobFactory(reporting, logger);
- const executeJobFn = await executeJobFactory(reporting, logger); // FIXME: does not "need" to be async
+ const scheduleTaskFn = scheduleTaskFnFactory(reporting, logger);
+ const runTaskFn = runTaskFnFactory(reporting, logger);
try {
- const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn(
+ const jobDocPayload: ScheduledTaskParamsPanelCsv = await scheduleTaskFn(
jobParams,
req.headers,
context,
@@ -74,13 +74,13 @@ export function registerGenerateCsvFromSavedObjectImmediate(
content_type: jobOutputContentType,
content: jobOutputContent,
size: jobOutputSize,
- }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req);
+ }: TaskRunResult = await runTaskFn(null, jobDocPayload, context, req);
logger.info(`Job output size: ${jobOutputSize} bytes`);
/*
* ESQueue worker function defaults `content` to null, even if the
- * executeJob returned undefined.
+ * runTask returned undefined.
*
* This converts null to undefined so the value can be sent to h.response()
*/
diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts
index e16f5278c8cc..93f79bfd892b 100644
--- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts
+++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts
@@ -10,7 +10,7 @@ import * as _ from 'lodash';
import { CSV_JOB_TYPE } from '../../../common/constants';
import { statuses } from '../../lib/esqueue/constants/statuses';
import { ExportTypesRegistry } from '../../lib/export_types_registry';
-import { ExportTypeDefinition, JobDocOutput, JobSource } from '../../types';
+import { ExportTypeDefinition, JobSource, TaskRunResult } from '../../types';
type ExportTypeType = ExportTypeDefinition;
@@ -18,7 +18,7 @@ interface ErrorFromPayload {
message: string;
}
-// A camelCase version of JobDocOutput
+// A camelCase version of TaskRunResult
interface Payload {
statusCode: number;
content: string | Buffer | ErrorFromPayload;
@@ -31,7 +31,7 @@ const DEFAULT_TITLE = 'report';
const getTitle = (exportType: ExportTypeType, title?: string): string =>
`${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`;
-const getReportingHeaders = (output: JobDocOutput, exportType: ExportTypeType) => {
+const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeType) => {
const metaDataHeaders: Record = {};
if (exportType.jobType === CSV_JOB_TYPE) {
@@ -55,7 +55,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist
}
}
- function getCompleted(output: JobDocOutput, jobType: string, title: string): Payload {
+ function getCompleted(output: TaskRunResult, jobType: string, title: string): Payload {
const exportType = exportTypesRegistry.get((item: ExportTypeType) => item.jobType === jobType);
const filename = getTitle(exportType, title);
const headers = getReportingHeaders(output, exportType);
@@ -73,7 +73,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist
// @TODO: These should be semantic HTTP codes as 500/503's indicate
// error then these are really operating properly.
- function getFailure(output: JobDocOutput): Payload {
+ function getFailure(output: TaskRunResult): Payload {
return {
statusCode: 500,
content: {
diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts
index 5eceed0a7f2a..607ce34ab946 100644
--- a/x-pack/plugins/reporting/server/routes/types.d.ts
+++ b/x-pack/plugins/reporting/server/routes/types.d.ts
@@ -6,7 +6,7 @@
import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server';
import { AuthenticatedUser } from '../../../security/server';
-import { JobDocPayload } from '../types';
+import { ScheduledTaskParams } from '../types';
export type HandlerFunction = (
user: AuthenticatedUser | null,
@@ -23,7 +23,7 @@ export interface QueuedJobPayload {
error?: boolean;
source: {
job: {
- payload: JobDocPayload;
+ payload: ScheduledTaskParams;
};
};
}
diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts
index 409a89899bee..96eef8167261 100644
--- a/x-pack/plugins/reporting/server/types.ts
+++ b/x-pack/plugins/reporting/server/types.ts
@@ -58,7 +58,7 @@ export interface JobParamPostPayload {
timerange: TimeRangeParams;
}
-export interface JobDocPayload {
+export interface ScheduledTaskParams {
headers?: string; // serialized encrypted headers
jobParams: JobParamsType;
title: string;
@@ -70,13 +70,13 @@ export interface JobSource {
_index: string;
_source: {
jobtype: string;
- output: JobDocOutput;
- payload: JobDocPayload;
+ output: TaskRunResult;
+ payload: ScheduledTaskParams;
status: JobStatus;
};
}
-export interface JobDocOutput {
+export interface TaskRunResult {
content_type: string;
content: string | null;
size: number;
@@ -173,43 +173,43 @@ export type ReportingSetup = object;
* Internal Types
*/
+export type CaptureConfig = ReportingConfigType['capture'];
+export type ScrollConfig = ReportingConfigType['csv']['scroll'];
+
export type ESQueueCreateJobFn = (
jobParams: JobParamsType,
context: RequestHandlerContext,
request: KibanaRequest
) => Promise;
-export type ESQueueWorkerExecuteFn = (
+export type ESQueueWorkerExecuteFn = (
jobId: string,
- job: JobDocPayloadType,
- cancellationToken?: CancellationToken
+ job: ScheduledTaskParamsType,
+ cancellationToken: CancellationToken
) => Promise;
-export type CaptureConfig = ReportingConfigType['capture'];
-export type ScrollConfig = ReportingConfigType['csv']['scroll'];
-
-export type CreateJobFactory = (
+export type ScheduleTaskFnFactory = (
reporting: ReportingCore,
logger: LevelLogger
-) => CreateJobFnType;
+) => ScheduleTaskFnType;
-export type ExecuteJobFactory = (
+export type RunTaskFnFactory = (
reporting: ReportingCore,
logger: LevelLogger
-) => Promise; // FIXME: does not "need" to be async
+) => RunTaskFnType;
export interface ExportTypeDefinition<
JobParamsType,
- CreateJobFnType,
+ ScheduleTaskFnType,
JobPayloadType,
- ExecuteJobFnType
+ RunTaskFnType
> {
id: string;
name: string;
jobType: string;
jobContentEncoding?: string;
jobContentExtension: string;
- createJobFactory: CreateJobFactory;
- executeJobFactory: ExecuteJobFactory;
+ scheduleTaskFnFactory: ScheduleTaskFnFactory;
+ runTaskFnFactory: RunTaskFnFactory;
validLicenses: string[];
}
From 21faa232ed6c509ec79773a94916ec08e4b8f7fb Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Thu, 18 Jun 2020 18:26:26 -0400
Subject: [PATCH 06/33] [Ingest Manager] Use long polling for agent checkin
(#68922) (#69561)
---
.../ingest_manager/common/constants/agent.ts | 2 +
.../ingest_manager/common/types/index.ts | 1 +
x-pack/plugins/ingest_manager/package.json | 5 +-
.../ingest_manager/server/constants/index.ts | 2 +
x-pack/plugins/ingest_manager/server/index.ts | 1 +
.../plugins/ingest_manager/server/plugin.ts | 4 +
.../server/routes/agent/handlers.ts | 40 ++--
.../server/routes/agent/index.ts | 6 +-
.../server/services/agent_config.ts | 2 -
.../server/services/agents/actions.ts | 9 +
.../server/services/agents/checkin.test.ts | 136 ------------
.../server/services/agents/checkin.ts | 193 ------------------
.../server/services/agents/checkin/index.ts | 110 ++++++++++
.../services/agents/checkin/rxjs_utils.ts | 43 ++++
.../server/services/agents/checkin/state.ts | 48 +++++
.../agents/checkin/state_connected_agents.ts | 79 +++++++
.../agents/checkin/state_new_actions.ts | 183 +++++++++++++++++
.../server/services/app_context.ts | 9 +-
x-pack/plugins/ingest_manager/yarn.lock | 1 +
.../apis/fleet/agents/checkin.ts | 3 +-
x-pack/test/api_integration/config.ts | 1 +
.../es_archives/fleet/agents/data.json | 2 +-
yarn.lock | 7 +
23 files changed, 534 insertions(+), 353 deletions(-)
delete mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts
delete mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
create mode 120000 x-pack/plugins/ingest_manager/yarn.lock
diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts
index f3990ba78c53..e9226fa68492 100644
--- a/x-pack/plugins/ingest_manager/common/constants/agent.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts
@@ -14,3 +14,5 @@ export const AGENT_TYPE_TEMPORARY = 'TEMPORARY';
export const AGENT_POLLING_THRESHOLD_MS = 30000;
export const AGENT_POLLING_INTERVAL = 1000;
+export const AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS = 30000;
+export const AGENT_UPDATE_ACTIONS_INTERVAL_MS = 5000;
diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts
index 800e77a6a274..7f81b04f5e84 100644
--- a/x-pack/plugins/ingest_manager/common/types/index.ts
+++ b/x-pack/plugins/ingest_manager/common/types/index.ts
@@ -15,6 +15,7 @@ export interface IngestManagerConfigType {
fleet: {
enabled: boolean;
tlsCheckDisabled: boolean;
+ pollingRequestTimeout: number;
kibana: {
host?: string;
ca_sha256?: string;
diff --git a/x-pack/plugins/ingest_manager/package.json b/x-pack/plugins/ingest_manager/package.json
index 052d3d0b42c7..8826ed57ab10 100644
--- a/x-pack/plugins/ingest_manager/package.json
+++ b/x-pack/plugins/ingest_manager/package.json
@@ -3,5 +3,8 @@
"name": "ingest-manager",
"version": "8.0.0",
"private": true,
- "license": "Elastic-License"
+ "license": "Elastic-License",
+ "dependencies": {
+ "abort-controller": "^3.0.0"
+ }
}
diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts
index 6e633c04ed81..4d60b9031414 100644
--- a/x-pack/plugins/ingest_manager/server/constants/index.ts
+++ b/x-pack/plugins/ingest_manager/server/constants/index.ts
@@ -9,6 +9,8 @@ export {
AGENT_TYPE_TEMPORARY,
AGENT_POLLING_THRESHOLD_MS,
AGENT_POLLING_INTERVAL,
+ AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS,
+ AGENT_UPDATE_ACTIONS_INTERVAL_MS,
INDEX_PATTERN_PLACEHOLDER_SUFFIX,
// Routes
PLUGIN_ID,
diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts
index 84408b60d3ed..f6b2d7ccc6d4 100644
--- a/x-pack/plugins/ingest_manager/server/index.ts
+++ b/x-pack/plugins/ingest_manager/server/index.ts
@@ -27,6 +27,7 @@ export const config = {
fleet: schema.object({
enabled: schema.boolean({ defaultValue: true }),
tlsCheckDisabled: schema.boolean({ defaultValue: false }),
+ pollingRequestTimeout: schema.number({ defaultValue: 60000 }),
kibana: schema.object({
host: schema.maybe(schema.string()),
ca_sha256: schema.maybe(schema.string()),
diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts
index 0dbaf13de75d..ba826e96e40b 100644
--- a/x-pack/plugins/ingest_manager/server/plugin.ts
+++ b/x-pack/plugins/ingest_manager/server/plugin.ts
@@ -55,6 +55,7 @@ import {
} from './services';
import { getAgentStatusById } from './services/agents';
import { CloudSetup } from '../../cloud/server';
+import { agentCheckinState } from './services/agents/checkin/state';
export interface IngestManagerSetupDeps {
licensing: LicensingPluginSetup;
@@ -229,6 +230,8 @@ export class IngestManagerPlugin
logger: this.logger,
});
licenseService.start(this.licensing$);
+ agentCheckinState.start();
+
return {
esIndexPatternService: new ESIndexPatternSavedObjectService(),
agentService: {
@@ -240,5 +243,6 @@ export class IngestManagerPlugin
public async stop() {
appContextService.stop();
licenseService.stop();
+ agentCheckinState.stop();
}
}
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
index ae833b55137c..0d1c77b8d697 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
@@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { RequestHandler, KibanaRequest } from 'src/core/server';
+import { RequestHandler } from 'src/core/server';
import { TypeOf } from '@kbn/config-schema';
+import { AbortController } from 'abort-controller';
import {
GetAgentsResponse,
GetOneAgentResponse,
@@ -32,13 +33,6 @@ import * as AgentService from '../../services/agents';
import * as APIKeyService from '../../services/api_keys';
import { appContextService } from '../../services/app_context';
-export function getInternalUserSOClient(request: KibanaRequest) {
- // soClient as kibana internal users, be carefull on how you use it, security is not enabled
- return appContextService.getSavedObjects().getScopedClient(request, {
- excludedWrappers: ['security'],
- });
-}
-
export const getAgentHandler: RequestHandler> = async (context, request, response) => {
@@ -176,14 +170,20 @@ export const postAgentCheckinHandler: RequestHandler<
TypeOf
> = async (context, request, response) => {
try {
- const soClient = getInternalUserSOClient(request);
+ const soClient = appContextService.getInternalUserSOClient(request);
const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
+ const abortController = new AbortController();
+ request.events.aborted$.subscribe(() => {
+ abortController.abort();
+ });
+ const signal = abortController.signal;
const { actions } = await AgentService.agentCheckin(
soClient,
agent,
request.body.events || [],
- request.body.local_metadata
+ request.body.local_metadata,
+ { signal }
);
const body: PostAgentCheckinResponse = {
action: 'checkin',
@@ -198,16 +198,24 @@ export const postAgentCheckinHandler: RequestHandler<
};
return response.ok({ body });
- } catch (e) {
- if (e.isBoom && e.output.statusCode === 404) {
- return response.notFound({
- body: { message: `Agent ${request.params.agentId} not found` },
+ } catch (err) {
+ const logger = appContextService.getLogger();
+ if (err.isBoom) {
+ if (err.output.statusCode >= 500) {
+ logger.error(err);
+ }
+
+ return response.customError({
+ statusCode: err.output.statusCode,
+ body: { message: err.output.payload.message },
});
}
+ logger.error(err);
+
return response.customError({
statusCode: 500,
- body: { message: e.message },
+ body: { message: err.message },
});
}
};
@@ -218,7 +226,7 @@ export const postAgentEnrollHandler: RequestHandler<
TypeOf
> = async (context, request, response) => {
try {
- const soClient = getInternalUserSOClient(request);
+ const soClient = appContextService.getInternalUserSOClient(request);
const { apiKeyId } = APIKeyService.parseApiKeyFromHeaders(request.headers);
const enrollmentAPIKey = await APIKeyService.getEnrollmentAPIKeyById(soClient, apiKeyId);
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
index 78bb178dce40..87eee4622c80 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
@@ -35,12 +35,12 @@ import {
postAgentEnrollHandler,
postAgentsUnenrollHandler,
getAgentStatusForConfigHandler,
- getInternalUserSOClient,
putAgentsReassignHandler,
} from './handlers';
import { postAgentAcksHandlerBuilder } from './acks_handlers';
import * as AgentService from '../../services/agents';
import { postNewAgentActionHandlerBuilder } from './actions_handlers';
+import { appContextService } from '../../services';
export const registerRoutes = (router: IRouter) => {
// Get one
@@ -110,7 +110,9 @@ export const registerRoutes = (router: IRouter) => {
postAgentAcksHandlerBuilder({
acknowledgeAgentActions: AgentService.acknowledgeAgentActions,
getAgentByAccessAPIKeyId: AgentService.getAgentByAccessAPIKeyId,
- getSavedObjectsClientContract: getInternalUserSOClient,
+ getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind(
+ appContextService
+ ),
saveAgentEvents: AgentService.saveAgentEvents,
})
);
diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts
index 9e0386de7476..9c27e9b7a3a7 100644
--- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts
@@ -65,8 +65,6 @@ class AgentConfigService {
updated_by: user ? user.username : 'system',
});
- await this.triggerAgentConfigUpdatedEvent(soClient, 'updated', id);
-
return (await this.get(soClient, id)) as AgentConfig;
}
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
index 236ad7df466b..8d1b320c89ae 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
@@ -77,6 +77,15 @@ export async function getAgentActionByIds(
);
}
+export async function getNewActionsSince(soClient: SavedObjectsClientContract, timestamp: string) {
+ const res = await soClient.find({
+ type: AGENT_ACTION_SAVED_OBJECT_TYPE,
+ filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * AND ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.created_at >= "${timestamp}"`,
+ });
+
+ return res.saved_objects.map(savedObjectToAgentAction);
+}
+
export interface ActionsService {
getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise;
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts
deleted file mode 100644
index 72a86d7c8158..000000000000
--- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { shouldCreateConfigAction } from './checkin';
-import { Agent } from '../../types';
-
-function getAgent(data: Partial) {
- return { actions: [], ...data } as Agent;
-}
-
-describe('Agent checkin service', () => {
- describe('shouldCreateConfigAction', () => {
- it('should return false if the agent do not have an assigned config', () => {
- const res = shouldCreateConfigAction(getAgent({}), []);
-
- expect(res).toBeFalsy();
- });
-
- it('should return true if this is agent first checkin', () => {
- const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' }), []);
-
- expect(res).toBeTruthy();
- });
-
- it('should return false agent is already running latest revision', () => {
- const res = shouldCreateConfigAction(
- getAgent({
- config_id: 'config1',
- last_checkin: '2018-01-02T00:00:00',
- config_revision: 1,
- config_newest_revision: 1,
- }),
- []
- );
-
- expect(res).toBeFalsy();
- });
-
- it('should return false agent has already latest revision config change action', () => {
- const res = shouldCreateConfigAction(
- getAgent({
- config_id: 'config1',
- last_checkin: '2018-01-02T00:00:00',
- config_revision: 1,
- config_newest_revision: 2,
- }),
- [
- {
- id: 'action1',
- agent_id: 'agent1',
- type: 'CONFIG_CHANGE',
- created_at: new Date().toISOString(),
- data: {
- config: {
- id: 'config1',
- revision: 2,
- },
- },
- },
- ]
- );
-
- expect(res).toBeFalsy();
- });
-
- it('should return true agent has unrelated config change actions', () => {
- const res = shouldCreateConfigAction(
- getAgent({
- config_id: 'config1',
- last_checkin: '2018-01-02T00:00:00',
- config_revision: 1,
- config_newest_revision: 2,
- }),
- [
- {
- id: 'action1',
- agent_id: 'agent1',
- type: 'CONFIG_CHANGE',
- created_at: new Date().toISOString(),
- data: {
- config: {
- id: 'config2',
- revision: 2,
- },
- },
- },
- {
- id: 'action1',
- agent_id: 'agent1',
- type: 'CONFIG_CHANGE',
- created_at: new Date().toISOString(),
- data: {
- config: {
- id: 'config1',
- revision: 1,
- },
- },
- },
- ]
- );
-
- expect(res).toBeTruthy();
- });
-
- it('should return true if this agent has a new revision', () => {
- const res = shouldCreateConfigAction(
- getAgent({
- config_id: 'config1',
- last_checkin: '2018-01-02T00:00:00',
- config_revision: 1,
- config_newest_revision: 2,
- }),
- []
- );
-
- expect(res).toBeTruthy();
- });
-
- it('should return true if this agent has no revision currently set', () => {
- const res = shouldCreateConfigAction(
- getAgent({
- config_id: 'config1',
- last_checkin: '2018-01-02T00:00:00',
- config_revision: null,
- config_newest_revision: 2,
- }),
- []
- );
-
- expect(res).toBeTruthy();
- });
- });
-});
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts
deleted file mode 100644
index 5bbc37605112..000000000000
--- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts
+++ /dev/null
@@ -1,193 +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 { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server';
-import {
- Agent,
- NewAgentEvent,
- AgentEvent,
- AgentAction,
- AgentSOAttributes,
- AgentEventSOAttributes,
- AgentMetadata,
-} from '../../types';
-
-import { agentConfigService } from '../agent_config';
-import * as APIKeysService from '../api_keys';
-import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants';
-import { getAgentActionsForCheckin, createAgentAction } from './actions';
-import { appContextService } from '../app_context';
-
-export async function agentCheckin(
- soClient: SavedObjectsClientContract,
- agent: Agent,
- events: NewAgentEvent[],
- localMetadata?: any
-) {
- const updateData: {
- last_checkin: string;
- default_api_key?: string;
- default_api_key_id?: string;
- local_metadata?: AgentMetadata;
- current_error_events?: string;
- } = {
- last_checkin: new Date().toISOString(),
- };
-
- const actions = await getAgentActionsForCheckin(soClient, agent.id);
-
- // Generate new agent config if config is updated
- if (agent.config_id && shouldCreateConfigAction(agent, actions)) {
- const {
- attributes: { default_api_key: defaultApiKey },
- } = await appContextService
- .getEncryptedSavedObjects()
- .getDecryptedAsInternalUser(AGENT_SAVED_OBJECT_TYPE, agent.id);
-
- const config = await agentConfigService.getFullConfig(soClient, agent.config_id);
- if (config) {
- // Assign output API keys
- // We currently only support default ouput
- if (!defaultApiKey) {
- const outputAPIKey = await APIKeysService.generateOutputApiKey(
- soClient,
- 'default',
- agent.id
- );
- updateData.default_api_key = outputAPIKey.key;
- updateData.default_api_key_id = outputAPIKey.id;
- }
- // Mutate the config to set the api token for this agent
- config.outputs.default.api_key = defaultApiKey || updateData.default_api_key;
-
- const configChangeAction = await createAgentAction(soClient, {
- agent_id: agent.id,
- type: 'CONFIG_CHANGE',
- data: { config } as any,
- created_at: new Date().toISOString(),
- sent_at: undefined,
- });
- actions.push(configChangeAction);
- }
- }
-
- const { updatedErrorEvents } = await processEventsForCheckin(soClient, agent, events);
-
- // Persist changes
- if (updatedErrorEvents) {
- updateData.current_error_events = JSON.stringify(updatedErrorEvents);
- }
-
- await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, updateData);
-
- return { actions };
-}
-
-async function processEventsForCheckin(
- soClient: SavedObjectsClientContract,
- agent: Agent,
- events: NewAgentEvent[]
-) {
- const acknowledgedActionIds: string[] = [];
- const updatedErrorEvents: Array = [...agent.current_error_events];
- for (const event of events) {
- // @ts-ignore
- event.config_id = agent.config_id;
-
- if (isActionEvent(event)) {
- acknowledgedActionIds.push(event.action_id as string);
- }
-
- if (isErrorOrState(event)) {
- // Remove any global or specific to a stream event
- const existingEventIndex = updatedErrorEvents.findIndex(
- (e) => e.stream_id === event.stream_id
- );
- if (existingEventIndex >= 0) {
- updatedErrorEvents.splice(existingEventIndex, 1);
- }
- if (event.type === 'ERROR') {
- updatedErrorEvents.push(event);
- }
- }
- }
-
- if (events.length > 0) {
- await createEventsForAgent(soClient, agent.id, events);
- }
-
- return {
- acknowledgedActionIds,
- updatedErrorEvents,
- };
-}
-
-async function createEventsForAgent(
- soClient: SavedObjectsClientContract,
- agentId: string,
- events: NewAgentEvent[]
-) {
- const objects: Array> = events.map(
- (eventData) => {
- return {
- attributes: {
- ...eventData,
- payload: eventData.payload ? JSON.stringify(eventData.payload) : undefined,
- },
- type: AGENT_EVENT_SAVED_OBJECT_TYPE,
- };
- }
- );
-
- return soClient.bulkCreate(objects);
-}
-
-function isErrorOrState(event: AgentEvent | NewAgentEvent) {
- return event.type === 'STATE' || event.type === 'ERROR';
-}
-
-function isActionEvent(event: AgentEvent | NewAgentEvent) {
- return (
- event.type === 'ACTION' && (event.subtype === 'ACKNOWLEDGED' || event.subtype === 'UNKNOWN')
- );
-}
-
-export function shouldCreateConfigAction(agent: Agent, actions: AgentAction[]): boolean {
- if (!agent.config_id) {
- return false;
- }
-
- const isFirstCheckin = !agent.last_checkin;
- if (isFirstCheckin) {
- return true;
- }
-
- const isAgentConfigOutdated =
- // Config reassignment
- (!agent.config_revision && agent.config_newest_revision) ||
- // new revision of a config
- (agent.config_revision &&
- agent.config_newest_revision &&
- agent.config_revision < agent.config_newest_revision);
-
- if (!isAgentConfigOutdated) {
- return false;
- }
-
- const isActionAlreadyGenerated = !!actions.find((action) => {
- if (!action.data || action.type !== 'CONFIG_CHANGE') {
- return false;
- }
-
- const { data } = action;
-
- return (
- data.config.id === agent.config_id && data.config.revision === agent.config_newest_revision
- );
- });
-
- return !isActionAlreadyGenerated;
-}
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts
new file mode 100644
index 000000000000..7c6641bbb5fa
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts
@@ -0,0 +1,110 @@
+/*
+ * 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 { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server';
+import {
+ Agent,
+ NewAgentEvent,
+ AgentEvent,
+ AgentSOAttributes,
+ AgentEventSOAttributes,
+ AgentMetadata,
+} from '../../../types';
+
+import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../../constants';
+import { agentCheckinState } from './state';
+import { getAgentActionsForCheckin } from '../actions';
+
+export async function agentCheckin(
+ soClient: SavedObjectsClientContract,
+ agent: Agent,
+ events: NewAgentEvent[],
+ localMetadata?: any,
+ options?: { signal: AbortSignal }
+) {
+ const updateData: {
+ local_metadata?: AgentMetadata;
+ current_error_events?: string;
+ } = {};
+ const { updatedErrorEvents } = await processEventsForCheckin(soClient, agent, events);
+ if (updatedErrorEvents) {
+ updateData.current_error_events = JSON.stringify(updatedErrorEvents);
+ }
+ if (localMetadata) {
+ updateData.local_metadata = localMetadata;
+ }
+ if (Object.keys(updateData).length > 0) {
+ await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, updateData);
+ }
+
+ // Check if some actions are not acknowledged
+ let actions = await getAgentActionsForCheckin(soClient, agent.id);
+ if (actions.length > 0) {
+ return { actions };
+ }
+
+ // Wait for new actions
+ actions = await agentCheckinState.subscribeToNewActions(soClient, agent, options);
+
+ return { actions };
+}
+
+async function processEventsForCheckin(
+ soClient: SavedObjectsClientContract,
+ agent: Agent,
+ events: NewAgentEvent[]
+) {
+ const updatedErrorEvents: Array = [...agent.current_error_events];
+ for (const event of events) {
+ // @ts-ignore
+ event.config_id = agent.config_id;
+
+ if (isErrorOrState(event)) {
+ // Remove any global or specific to a stream event
+ const existingEventIndex = updatedErrorEvents.findIndex(
+ (e) => e.stream_id === event.stream_id
+ );
+ if (existingEventIndex >= 0) {
+ updatedErrorEvents.splice(existingEventIndex, 1);
+ }
+ if (event.type === 'ERROR') {
+ updatedErrorEvents.push(event);
+ }
+ }
+ }
+
+ if (events.length > 0) {
+ await createEventsForAgent(soClient, agent.id, events);
+ }
+
+ return {
+ updatedErrorEvents,
+ };
+}
+
+async function createEventsForAgent(
+ soClient: SavedObjectsClientContract,
+ agentId: string,
+ events: NewAgentEvent[]
+) {
+ const objects: Array> = events.map(
+ (eventData) => {
+ return {
+ attributes: {
+ ...eventData,
+ payload: eventData.payload ? JSON.stringify(eventData.payload) : undefined,
+ },
+ type: AGENT_EVENT_SAVED_OBJECT_TYPE,
+ };
+ }
+ );
+
+ return soClient.bulkCreate(objects);
+}
+
+function isErrorOrState(event: AgentEvent | NewAgentEvent) {
+ return event.type === 'STATE' || event.type === 'ERROR';
+}
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts
new file mode 100644
index 000000000000..1f9bba8b12be
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { Observable } from 'rxjs';
+
+export class AbortError extends Error {}
+
+export const toPromiseAbortable = (
+ observable: Observable,
+ signal?: AbortSignal
+): Promise =>
+ new Promise((resolve, reject) => {
+ if (signal && signal.aborted) {
+ reject(new AbortError('Aborted'));
+ return;
+ }
+
+ const listener = () => {
+ subscription.unsubscribe();
+ reject(new AbortError('Aborted'));
+ };
+ const cleanup = () => {
+ if (signal) {
+ signal.removeEventListener('abort', listener);
+ }
+ };
+ const subscription = observable.subscribe(
+ (data) => {
+ cleanup();
+ resolve(data);
+ },
+ (err) => {
+ cleanup();
+ reject(err);
+ }
+ );
+
+ if (signal) {
+ signal.addEventListener('abort', listener, { once: true });
+ }
+ });
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts
new file mode 100644
index 000000000000..69d61171b21f
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SavedObjectsClientContract } from 'src/core/server';
+import { Agent } from '../../../types';
+import { appContextService } from '../../app_context';
+import { agentCheckinStateConnectedAgentsFactory } from './state_connected_agents';
+import { agentCheckinStateNewActionsFactory } from './state_new_actions';
+import { AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS } from '../../../constants';
+
+function agentCheckinStateFactory() {
+ const agentConnected = agentCheckinStateConnectedAgentsFactory();
+ const newActions = agentCheckinStateNewActionsFactory();
+ let interval: NodeJS.Timeout;
+ function start() {
+ interval = setInterval(async () => {
+ try {
+ await agentConnected.updateLastCheckinAt();
+ } catch (err) {
+ appContextService.getLogger().error(err);
+ }
+ }, AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS);
+ }
+
+ function stop() {
+ if (interval) {
+ clearInterval(interval);
+ }
+ }
+ return {
+ subscribeToNewActions: (
+ soClient: SavedObjectsClientContract,
+ agent: Agent,
+ options?: { signal: AbortSignal }
+ ) =>
+ agentConnected.wrapPromise(
+ agent.id,
+ newActions.subscribeToNewActions(soClient, agent, options)
+ ),
+ start,
+ stop,
+ };
+}
+
+export const agentCheckinState = agentCheckinStateFactory();
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts
new file mode 100644
index 000000000000..96e006b78f00
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest, SavedObjectsBulkUpdateObject } from 'src/core/server';
+import { appContextService } from '../../app_context';
+import { AgentSOAttributes } from '../../../types';
+import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants';
+
+function getInternalUserSOClient() {
+ const fakeRequest = ({
+ headers: {},
+ getBasePath: () => '',
+ path: '/',
+ route: { settings: {} },
+ url: {
+ href: '/',
+ },
+ raw: {
+ req: {
+ url: '/',
+ },
+ },
+ } as unknown) as KibanaRequest;
+
+ return appContextService.getInternalUserSOClient(fakeRequest);
+}
+export function agentCheckinStateConnectedAgentsFactory() {
+ const connectedAgentsIds = new Set();
+ let agentToUpdate = new Set();
+
+ function addAgent(agentId: string) {
+ connectedAgentsIds.add(agentId);
+ agentToUpdate.add(agentId);
+ }
+
+ function removeAgent(agentId: string) {
+ connectedAgentsIds.delete(agentId);
+ }
+
+ async function wrapPromise(agentId: string, p: Promise): Promise {
+ try {
+ addAgent(agentId);
+ const res = await p;
+ removeAgent(agentId);
+ return res;
+ } catch (err) {
+ removeAgent(agentId);
+ throw err;
+ }
+ }
+
+ async function updateLastCheckinAt() {
+ if (agentToUpdate.size === 0) {
+ return;
+ }
+ const internalSOClient = getInternalUserSOClient();
+ const now = new Date().toISOString();
+ const updates: Array> = [
+ ...connectedAgentsIds.values(),
+ ].map((agentId) => ({
+ type: AGENT_SAVED_OBJECT_TYPE,
+ id: agentId,
+ attributes: {
+ last_checkin: now,
+ },
+ }));
+
+ agentToUpdate = new Set([...connectedAgentsIds.values()]);
+ await internalSOClient.bulkUpdate(updates, { refresh: false });
+ }
+
+ return {
+ wrapPromise,
+ updateLastCheckinAt,
+ };
+}
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
new file mode 100644
index 000000000000..0f30ab409f38
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
@@ -0,0 +1,183 @@
+/*
+ * 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 { timer, from, Observable, TimeoutError } from 'rxjs';
+import {
+ shareReplay,
+ distinctUntilKeyChanged,
+ switchMap,
+ mergeMap,
+ merge,
+ filter,
+ timeout,
+ take,
+} from 'rxjs/operators';
+import { SavedObjectsClientContract, KibanaRequest } from 'src/core/server';
+import {
+ Agent,
+ AgentAction,
+ AgentSOAttributes,
+ AgentConfig,
+ FullAgentConfig,
+} from '../../../types';
+import { agentConfigService } from '../../agent_config';
+import * as APIKeysService from '../../api_keys';
+import { AGENT_SAVED_OBJECT_TYPE, AGENT_UPDATE_ACTIONS_INTERVAL_MS } from '../../../constants';
+import { createAgentAction, getNewActionsSince } from '../actions';
+import { appContextService } from '../../app_context';
+import { toPromiseAbortable, AbortError } from './rxjs_utils';
+
+function getInternalUserSOClient() {
+ const fakeRequest = ({
+ headers: {},
+ getBasePath: () => '',
+ path: '/',
+ route: { settings: {} },
+ url: {
+ href: '/',
+ },
+ raw: {
+ req: {
+ url: '/',
+ },
+ },
+ } as unknown) as KibanaRequest;
+
+ return appContextService.getInternalUserSOClient(fakeRequest);
+}
+
+function createAgentConfigSharedObservable(configId: string) {
+ const internalSOClient = getInternalUserSOClient();
+ return timer(0, AGENT_UPDATE_ACTIONS_INTERVAL_MS).pipe(
+ switchMap(() =>
+ from(agentConfigService.get(internalSOClient, configId) as Promise)
+ ),
+ distinctUntilKeyChanged('revision'),
+ switchMap((data) => from(agentConfigService.getFullConfig(internalSOClient, configId))),
+ shareReplay({ refCount: true, bufferSize: 1 })
+ );
+}
+
+function createNewActionsSharedObservable(): Observable {
+ return timer(0, AGENT_UPDATE_ACTIONS_INTERVAL_MS).pipe(
+ switchMap(() => {
+ const internalSOClient = getInternalUserSOClient();
+
+ return from(getNewActionsSince(internalSOClient, new Date().toISOString()));
+ }),
+ shareReplay({ refCount: true, bufferSize: 1 })
+ );
+}
+
+async function getOrCreateAgentDefaultOutputAPIKey(
+ soClient: SavedObjectsClientContract,
+ agent: Agent
+): Promise {
+ const {
+ attributes: { default_api_key: defaultApiKey },
+ } = await appContextService
+ .getEncryptedSavedObjects()
+ .getDecryptedAsInternalUser(AGENT_SAVED_OBJECT_TYPE, agent.id);
+
+ if (defaultApiKey) {
+ return defaultApiKey;
+ }
+
+ const outputAPIKey = await APIKeysService.generateOutputApiKey(soClient, 'default', agent.id);
+ await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, {
+ default_api_key: outputAPIKey.key,
+ default_api_key_id: outputAPIKey.id,
+ });
+
+ return outputAPIKey.key;
+}
+
+async function createAgentActionFromConfigIfOutdated(
+ soClient: SavedObjectsClientContract,
+ agent: Agent,
+ config: FullAgentConfig | null
+) {
+ if (!config || !config.revision) {
+ return;
+ }
+ const isAgentConfigOutdated = !agent.config_revision || agent.config_revision < config.revision;
+ if (!isAgentConfigOutdated) {
+ return;
+ }
+
+ // Deep clone !not supporting Date, and undefined value.
+ const newConfig = JSON.parse(JSON.stringify(config));
+
+ // Mutate the config to set the api token for this agent
+ newConfig.outputs.default.api_key = await getOrCreateAgentDefaultOutputAPIKey(soClient, agent);
+
+ const configChangeAction = await createAgentAction(soClient, {
+ agent_id: agent.id,
+ type: 'CONFIG_CHANGE',
+ data: { config: newConfig } as any,
+ created_at: new Date().toISOString(),
+ sent_at: undefined,
+ });
+
+ return [configChangeAction];
+}
+
+export function agentCheckinStateNewActionsFactory() {
+ // Shared Observables
+ const agentConfigs$ = new Map>();
+ const newActions$ = createNewActionsSharedObservable();
+
+ async function subscribeToNewActions(
+ soClient: SavedObjectsClientContract,
+ agent: Agent,
+ options?: { signal: AbortSignal }
+ ): Promise {
+ if (!agent.config_id) {
+ throw new Error('Agent do not have a config');
+ }
+ const configId = agent.config_id;
+ if (!agentConfigs$.has(configId)) {
+ agentConfigs$.set(configId, createAgentConfigSharedObservable(configId));
+ }
+ const agentConfig$ = agentConfigs$.get(configId);
+ if (!agentConfig$) {
+ throw new Error(`Invalid state no observable for config ${configId}`);
+ }
+ const stream$ = agentConfig$.pipe(
+ timeout(appContextService.getConfig()?.fleet.pollingRequestTimeout || 0),
+ mergeMap((config) => createAgentActionFromConfigIfOutdated(soClient, agent, config)),
+ merge(newActions$),
+ mergeMap(async (data) => {
+ if (!data) {
+ return;
+ }
+ const newActions = data.filter((action) => action.agent_id);
+ if (newActions.length === 0) {
+ return;
+ }
+
+ return newActions;
+ }),
+ filter((data) => data !== undefined),
+ take(1)
+ );
+ try {
+ const data = await toPromiseAbortable(stream$, options?.signal);
+
+ return data || [];
+ } catch (err) {
+ if (err instanceof TimeoutError || err instanceof AbortError) {
+ return [];
+ }
+
+ throw err;
+ }
+ }
+
+ return {
+ subscribeToNewActions,
+ };
+}
diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/ingest_manager/server/services/app_context.ts
index 81a16caa8ce9..5ed6f7c5e54d 100644
--- a/x-pack/plugins/ingest_manager/server/services/app_context.ts
+++ b/x-pack/plugins/ingest_manager/server/services/app_context.ts
@@ -5,7 +5,7 @@
*/
import { BehaviorSubject, Observable } from 'rxjs';
import { first } from 'rxjs/operators';
-import { SavedObjectsServiceStart, HttpServiceSetup, Logger } from 'src/core/server';
+import { SavedObjectsServiceStart, HttpServiceSetup, Logger, KibanaRequest } from 'src/core/server';
import {
EncryptedSavedObjectsClient,
EncryptedSavedObjectsPluginSetup,
@@ -89,6 +89,13 @@ class AppContextService {
return this.savedObjects;
}
+ public getInternalUserSOClient(request: KibanaRequest) {
+ // soClient as kibana internal users, be carefull on how you use it, security is not enabled
+ return appContextService.getSavedObjects().getScopedClient(request, {
+ excludedWrappers: ['security'],
+ });
+ }
+
public getIsProductionMode() {
return this.isProductionMode;
}
diff --git a/x-pack/plugins/ingest_manager/yarn.lock b/x-pack/plugins/ingest_manager/yarn.lock
new file mode 120000
index 000000000000..6e09764ec763
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/yarn.lock
@@ -0,0 +1 @@
+../../../yarn.lock
\ No newline at end of file
diff --git a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts
index 47756b02a8d2..d24f7f495a06 100644
--- a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts
+++ b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts
@@ -8,7 +8,7 @@ import expect from '@kbn/expect';
import uuid from 'uuid';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { getSupertestWithoutAuth } from './services';
+import { getSupertestWithoutAuth, setupIngest } from './services';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
@@ -44,6 +44,7 @@ export default function (providerContext: FtrProviderContext) {
},
});
});
+ setupIngest(providerContext);
after(async () => {
await esArchiver.unload('fleet/agents');
});
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index 71da903d33b2..4a333869a954 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -29,6 +29,7 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
'--optimize.enabled=false',
'--telemetry.optIn=true',
'--xpack.ingestManager.enabled=true',
+ '--xpack.ingestManager.fleet.pollingRequestTimeout=5000', // 5 seconds
'--xpack.securitySolution.alertResultListDefaultDateRange.from=2018-01-10T00:00:00.000Z',
],
},
diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json
index 1739f583b2e8..047f26a3f443 100644
--- a/x-pack/test/functional/es_archives/fleet/agents/data.json
+++ b/x-pack/test/functional/es_archives/fleet/agents/data.json
@@ -9,7 +9,7 @@
"access_api_key_id": "api-key-2",
"active": true,
"shared_id": "agent1_filebeat",
- "config_id": "1",
+ "config_id": "config1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
diff --git a/yarn.lock b/yarn.lock
index c3506b22520a..e9b057799337 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6359,6 +6359,13 @@ abort-controller@^2.0.3:
dependencies:
event-target-shim "^5.0.0"
+abort-controller@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+ integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+ dependencies:
+ event-target-shim "^5.0.0"
+
abortcontroller-polyfill@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4"
From 4d00d34ea8d9a23f6cbbd19a8f931af30cb4b9ba Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Thu, 18 Jun 2020 16:32:29 -0600
Subject: [PATCH 07/33] [SIEM][Detection Engine] Fixes 7.8 and 7.9 upgrade
issue within rules where you can get the error "params invalid: [lists]:
definition for this key is missing" (#69569)
## Summary
* https://github.com/elastic/kibana/issues/69463
* See here for manual backport to 7.8: https://github.com/elastic/kibana/pull/69434
This fixes a bug where if you import rules and set your overwrite to `true` multiple times in a row within 7.7 you can end up with a lists array. When upgrading to 7.8, we change the name of `lists` to `exceptions_lists` and suddenly when you enable/disable a rule you can get the following error below:
![image](https://user-images.githubusercontent.com/1151048/84945824-fa60e280-b0a4-11ea-8e05-bffdec2e4765.png)
The fix is to allow the lists array still if it is present within saved objects to avoid seeing this error screen and being tolerant. We also fix the area of code that is causing the data bug so it cannot happen again with `exceptions_list` which is what the name of lists was renamed to causing this problem.
Note that this has unit tests and I also manually tested this by intentionally injecting a `lists` and `exceptions_lists` and using the UI to verify there wasn't another validation spot that needed to be relaxed to allow for the data.
### Checklist
- [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
---
.../routes/rules/utils.test.ts | 29 ++++-
.../signals/signal_params_schema.mock.ts | 45 ++++++++
.../signals/signal_params_schema.test.ts | 105 ++++++++++++++++++
.../signals/signal_params_schema.ts | 2 +
4 files changed, 180 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.test.ts
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts
index 3b514b92e147..891c241661a1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts
@@ -115,7 +115,7 @@ describe('utils', () => {
expect(rule).toEqual(expected);
});
- it('transforms ML Rule fields', () => {
+ test('transforms ML Rule fields', () => {
const mlRule = getResult();
mlRule.params.anomalyThreshold = 55;
mlRule.params.machineLearningJobId = 'some_job_id';
@@ -130,6 +130,33 @@ describe('utils', () => {
})
);
});
+
+ // This has to stay here until we do data migration of saved objects and lists is removed from:
+ // signal_params_schema.ts
+ test('does not leak a lists structure in the transform which would cause validation issues', () => {
+ const result: RuleAlertType & { lists: [] } = { lists: [], ...getResult() };
+ const rule = transformAlertToRule(result);
+ expect(rule).toEqual(
+ expect.not.objectContaining({
+ lists: [],
+ })
+ );
+ });
+
+ // This has to stay here until we do data migration of saved objects and exceptions_list is removed from:
+ // signal_params_schema.ts
+ test('does not leak an exceptions_list structure in the transform which would cause validation issues', () => {
+ const result: RuleAlertType & { exceptions_list: [] } = {
+ exceptions_list: [],
+ ...getResult(),
+ };
+ const rule = transformAlertToRule(result);
+ expect(rule).toEqual(
+ expect.not.objectContaining({
+ exceptions_list: [],
+ })
+ );
+ });
});
describe('getIdError', () => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts
new file mode 100644
index 000000000000..d60509b28f7d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SignalParamsSchema } from './signal_params_schema';
+
+export const getSignalParamsSchemaMock = (): Partial => ({
+ description: 'Detecting root and admin users',
+ query: 'user.name: root or user.name: admin',
+ severity: 'high',
+ type: 'query',
+ riskScore: 55,
+ language: 'kuery',
+ ruleId: 'rule-1',
+ from: 'now-6m',
+ to: 'now',
+});
+
+export const getSignalParamsSchemaDecodedMock = (): SignalParamsSchema => ({
+ description: 'Detecting root and admin users',
+ falsePositives: [],
+ filters: null,
+ from: 'now-6m',
+ immutable: false,
+ index: null,
+ language: 'kuery',
+ maxSignals: 100,
+ meta: null,
+ note: null,
+ outputIndex: null,
+ query: 'user.name: root or user.name: admin',
+ references: [],
+ riskScore: 55,
+ ruleId: 'rule-1',
+ savedId: null,
+ severity: 'high',
+ threat: null,
+ timelineId: null,
+ timelineTitle: null,
+ to: 'now',
+ type: 'query',
+ version: 1,
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.test.ts
new file mode 100644
index 000000000000..a7404b406638
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.test.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { signalParamsSchema, SignalParamsSchema } from './signal_params_schema';
+import {
+ getSignalParamsSchemaDecodedMock,
+ getSignalParamsSchemaMock,
+} from './signal_params_schema.mock';
+import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants';
+
+describe('signal_params_schema', () => {
+ test('it works with expected basic mock data set', () => {
+ const schema = signalParamsSchema();
+ expect(schema.validate(getSignalParamsSchemaMock())).toEqual(
+ getSignalParamsSchemaDecodedMock()
+ );
+ });
+
+ test('it works on older lists data structures if they exist as an empty array', () => {
+ const schema = signalParamsSchema();
+ const mock: Partial = { lists: [], ...getSignalParamsSchemaMock() };
+ const expected: Partial = {
+ lists: [],
+ ...getSignalParamsSchemaDecodedMock(),
+ };
+ expect(schema.validate(mock)).toEqual(expected);
+ });
+
+ test('it works on older exceptions_list data structures if they exist as an empty array', () => {
+ const schema = signalParamsSchema();
+ const mock: Partial = {
+ exceptions_list: [],
+ ...getSignalParamsSchemaMock(),
+ };
+ const expected: Partial = {
+ exceptions_list: [],
+ ...getSignalParamsSchemaDecodedMock(),
+ };
+ expect(schema.validate(mock)).toEqual(expected);
+ });
+
+ test('it throws if given an invalid value', () => {
+ const schema = signalParamsSchema();
+ const mock: Partial & { madeUpValue: string } = {
+ madeUpValue: 'something',
+ ...getSignalParamsSchemaMock(),
+ };
+ expect(() => schema.validate(mock)).toThrow(
+ '[madeUpValue]: definition for this key is missing'
+ );
+ });
+
+ test('if risk score is a string then it will be converted into a number before being inserted as data', () => {
+ const schema = signalParamsSchema();
+ const mock: Omit, 'riskScore'> & { riskScore: string } = {
+ ...getSignalParamsSchemaMock(),
+ riskScore: '5',
+ };
+ expect(schema.validate(mock).riskScore).toEqual(5);
+ expect(typeof schema.validate(mock).riskScore).toEqual('number');
+ });
+
+ test('if risk score is a number then it will work as a number', () => {
+ const schema = signalParamsSchema();
+ const mock: Partial = {
+ ...getSignalParamsSchemaMock(),
+ riskScore: 5,
+ };
+ expect(schema.validate(mock).riskScore).toEqual(5);
+ expect(typeof schema.validate(mock).riskScore).toEqual('number');
+ });
+
+ test('maxSignals will default to "DEFAULT_MAX_SIGNALS" if not set', () => {
+ const schema = signalParamsSchema();
+ const { maxSignals, ...withoutMockData } = getSignalParamsSchemaMock();
+ expect(schema.validate(withoutMockData).maxSignals).toEqual(DEFAULT_MAX_SIGNALS);
+ });
+
+ test('version will default to "1" if not set', () => {
+ const schema = signalParamsSchema();
+ const { version, ...withoutVersion } = getSignalParamsSchemaMock();
+ expect(schema.validate(withoutVersion).version).toEqual(1);
+ });
+
+ test('references will default to an empty array if not set', () => {
+ const schema = signalParamsSchema();
+ const { references, ...withoutReferences } = getSignalParamsSchemaMock();
+ expect(schema.validate(withoutReferences).references).toEqual([]);
+ });
+
+ test('immutable will default to false if not set', () => {
+ const schema = signalParamsSchema();
+ const { immutable, ...withoutImmutable } = getSignalParamsSchemaMock();
+ expect(schema.validate(withoutImmutable).immutable).toEqual(false);
+ });
+
+ test('falsePositives will default to an empty array if not set', () => {
+ const schema = signalParamsSchema();
+ const { falsePositives, ...withoutFalsePositives } = getSignalParamsSchemaMock();
+ expect(schema.validate(withoutFalsePositives).falsePositives).toEqual([]);
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
index 461b2589babc..5f95f635a6bd 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
@@ -34,6 +34,8 @@ const signalSchema = schema.object({
type: schema.string(),
references: schema.arrayOf(schema.string(), { defaultValue: [] }),
version: schema.number({ defaultValue: 1 }),
+ lists: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), // For backwards compatibility with customers that had a data bug in 7.7. Once we use a migration script please remove this.
+ exceptions_list: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), // For backwards compatibility with customers that had a data bug in 7.8. Once we use a migration script please remove this.
exceptionsList: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
});
From 2fd1857fa738cca1560ffcda97d7a7d545756ff5 Mon Sep 17 00:00:00 2001
From: Marshall Main <55718608+marshallmain@users.noreply.github.com>
Date: Thu, 18 Jun 2020 18:35:58 -0400
Subject: [PATCH 08/33] Update endpoint event and alert types (#69292) (#69578)
* start redoing types
* finish redoing types
* fix bad test
* rework tests
* fix more types
* fix test
* Fix endpoints test and render error
* add deletePolicyStream to alerts api tests
Co-authored-by: Elastic Machine
Co-authored-by: Paul Tavares
Co-authored-by: Elastic Machine
Co-authored-by: Paul Tavares
---
.../common/endpoint/generate_data.test.ts | 12 +-
.../common/endpoint/generate_data.ts | 189 +++++++++---------
.../common/endpoint/index_data.ts | 92 +++++++++
.../common/endpoint/types.ts | 106 +++++-----
.../view/details/metadata/file_accordion.tsx | 2 +-
.../details/metadata/general_accordion.tsx | 2 +-
.../metadata/source_process_accordion.tsx | 8 +-
.../source_process_token_accordion.tsx | 4 +-
.../public/endpoint_alerts/view/index.tsx | 2 +-
.../pages/endpoint_hosts/store/selectors.ts | 4 +-
.../view/details/host_details.tsx | 8 +-
.../pages/endpoint_hosts/view/index.test.tsx | 65 ++++--
.../pages/endpoint_hosts/view/index.tsx | 8 +-
.../scripts/endpoint/resolver_generator.ts | 74 ++-----
.../endpoint/routes/metadata/metadata.test.ts | 2 +-
.../apis/endpoint/alerts/index.ts | 111 +++++++---
.../api_integration/apis/endpoint/metadata.ts | 11 +-
.../api_integration/apis/endpoint/policy.ts | 2 +-
.../endpoint/metadata/api_feature/data.json | 54 +++--
.../es_archives/endpoint/policy/data.json.gz | Bin 1326 -> 1327 bytes
20 files changed, 456 insertions(+), 300 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/endpoint/index_data.ts
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
index ba4f2251564e..d7b653916970 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
@@ -47,7 +47,7 @@ describe('data generator', () => {
const metadata = generator.generateHostMetadata(timestamp);
expect(metadata['@timestamp']).toEqual(timestamp);
expect(metadata.event.created).toEqual(timestamp);
- expect(metadata.endpoint).not.toBeNull();
+ expect(metadata.Endpoint).not.toBeNull();
expect(metadata.agent).not.toBeNull();
expect(metadata.host).not.toBeNull();
});
@@ -57,10 +57,10 @@ describe('data generator', () => {
const hostPolicyResponse = generator.generatePolicyResponse(timestamp);
expect(hostPolicyResponse['@timestamp']).toEqual(timestamp);
expect(hostPolicyResponse.event.created).toEqual(timestamp);
- expect(hostPolicyResponse.endpoint).not.toBeNull();
+ expect(hostPolicyResponse.Endpoint).not.toBeNull();
expect(hostPolicyResponse.agent).not.toBeNull();
expect(hostPolicyResponse.host).not.toBeNull();
- expect(hostPolicyResponse.endpoint.policy.applied).not.toBeNull();
+ expect(hostPolicyResponse.Endpoint.policy.applied).not.toBeNull();
});
it('creates alert event documents', () => {
@@ -68,7 +68,7 @@ describe('data generator', () => {
const alert = generator.generateAlert(timestamp);
expect(alert['@timestamp']).toEqual(timestamp);
expect(alert.event.action).not.toBeNull();
- expect(alert.endpoint).not.toBeNull();
+ expect(alert.Endpoint).not.toBeNull();
expect(alert.agent).not.toBeNull();
expect(alert.host).not.toBeNull();
expect(alert.process.entity_id).not.toBeNull();
@@ -364,7 +364,9 @@ describe('data generator', () => {
it('creates full resolver tree', () => {
const alertAncestors = 3;
const generations = 2;
- const events = [...generator.fullResolverTreeGenerator(alertAncestors, generations)];
+ const events = [
+ ...generator.fullResolverTreeGenerator({ ancestors: alertAncestors, generations }),
+ ];
const rootNode = buildResolverTree(events);
const visitedEvents = countResolverEvents(rootNode, alertAncestors + generations);
expect(visitedEvents).toEqual(events.length);
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index 7944d7d365ed..8c0328599677 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -10,7 +10,7 @@ import {
EndpointEvent,
Host,
HostMetadata,
- HostOS,
+ OSFields,
HostPolicyResponse,
HostPolicyResponseActionStatus,
PolicyData,
@@ -28,38 +28,46 @@ interface EventOptions {
processName?: string;
}
-const Windows: HostOS[] = [
+const Windows: OSFields[] = [
{
name: 'windows 10.0',
full: 'Windows 10',
version: '10.0',
- variant: 'Windows Pro',
+ Ext: {
+ variant: 'Windows Pro',
+ },
},
{
name: 'windows 10.0',
full: 'Windows Server 2016',
version: '10.0',
- variant: 'Windows Server',
+ Ext: {
+ variant: 'Windows Server',
+ },
},
{
name: 'windows 6.2',
full: 'Windows Server 2012',
version: '6.2',
- variant: 'Windows Server',
+ Ext: {
+ variant: 'Windows Server',
+ },
},
{
name: 'windows 6.3',
full: 'Windows Server 2012R2',
version: '6.3',
- variant: 'Windows Server Release 2',
+ Ext: {
+ variant: 'Windows Server Release 2',
+ },
},
];
-const Linux: HostOS[] = [];
+const Linux: OSFields[] = [];
-const Mac: HostOS[] = [];
+const Mac: OSFields[] = [];
-const OS: HostOS[] = [...Windows, ...Mac, ...Linux];
+const OS: OSFields[] = [...Windows, ...Mac, ...Linux];
const APPLIED_POLICIES: Array<{
name: string;
@@ -186,7 +194,7 @@ interface HostInfo {
type: string;
};
host: Host;
- endpoint: {
+ Endpoint: {
policy: {
applied: {
id: string;
@@ -283,8 +291,8 @@ export class EndpointDocGenerator {
* Creates new random policy id for the host to simulate new policy application
*/
public updatePolicyId() {
- this.commonInfo.endpoint.policy.applied.id = this.randomChoice(APPLIED_POLICIES).id;
- this.commonInfo.endpoint.policy.applied.status = this.randomChoice([
+ this.commonInfo.Endpoint.policy.applied = this.randomChoice(APPLIED_POLICIES);
+ this.commonInfo.Endpoint.policy.applied.status = this.randomChoice([
HostPolicyResponseActionStatus.success,
HostPolicyResponseActionStatus.failure,
HostPolicyResponseActionStatus.warning,
@@ -310,7 +318,7 @@ export class EndpointDocGenerator {
mac: this.randomArray(3, () => this.randomMac()),
os: this.randomChoice(OS),
},
- endpoint: {
+ Endpoint: {
policy: {
applied: this.randomChoice(APPLIED_POLICIES),
},
@@ -371,77 +379,88 @@ export class EndpointDocGenerator {
sha1: 'fake file sha1',
sha256: 'fake file sha256',
},
- code_signature: {
- trusted: false,
- subject_name: 'bad signer',
- },
- malware_classification: {
- identifier: 'endpointpe',
- score: 1,
- threshold: 0.66,
- version: '3.0.33',
+ Ext: {
+ code_signature: [
+ {
+ trusted: false,
+ subject_name: 'bad signer',
+ },
+ ],
+ malware_classification: {
+ identifier: 'endpointpe',
+ score: 1,
+ threshold: 0.66,
+ version: '3.0.33',
+ },
+ temp_file_path: 'C:/temp/fake_malware.exe',
},
- temp_file_path: 'C:/temp/fake_malware.exe',
},
process: {
pid: 2,
name: 'malware writer',
start: ts,
uptime: 0,
- user: 'SYSTEM',
entity_id: entityID,
executable: 'C:/malware.exe',
parent: parentEntityID ? { entity_id: parentEntityID, pid: 1 } : undefined,
- token: {
- domain: 'NT AUTHORITY',
- integrity_level: 16384,
- integrity_level_name: 'system',
- privileges: [
- {
- description: 'Replace a process level token',
- enabled: false,
- name: 'SeAssignPrimaryTokenPrivilege',
- },
- ],
- sid: 'S-1-5-18',
- type: 'tokenPrimary',
- user: 'SYSTEM',
- },
- code_signature: {
- trusted: false,
- subject_name: 'bad signer',
- },
hash: {
md5: 'fake md5',
sha1: 'fake sha1',
sha256: 'fake sha256',
},
+ Ext: {
+ code_signature: [
+ {
+ trusted: false,
+ subject_name: 'bad signer',
+ },
+ ],
+ user: 'SYSTEM',
+ token: {
+ domain: 'NT AUTHORITY',
+ integrity_level: 16384,
+ integrity_level_name: 'system',
+ privileges: [
+ {
+ description: 'Replace a process level token',
+ enabled: false,
+ name: 'SeAssignPrimaryTokenPrivilege',
+ },
+ ],
+ sid: 'S-1-5-18',
+ type: 'tokenPrimary',
+ user: 'SYSTEM',
+ },
+ },
},
dll: [
{
pe: {
architecture: 'x64',
- imphash: 'c30d230b81c734e82e86e2e2fe01cd01',
},
code_signature: {
subject_name: 'Cybereason Inc',
trusted: true,
},
- compile_time: 1534424710,
+
hash: {
md5: '1f2d082566b0fc5f2c238a5180db7451',
sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
},
- malware_classification: {
- identifier: 'Whitelisted',
- score: 0,
- threshold: 0,
- version: '3.0.0',
- },
- mapped_address: 5362483200,
- mapped_size: 0,
+
path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
+ Ext: {
+ compile_time: 1534424710,
+ mapped_address: 5362483200,
+ mapped_size: 0,
+ malware_classification: {
+ identifier: 'Whitelisted',
+ score: 0,
+ threshold: 0,
+ version: '3.0.0',
+ },
+ },
},
],
};
@@ -561,28 +580,9 @@ export class EndpointDocGenerator {
* @param percentTerminated - percent of nodes which will have process termination events
* @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children
*/
- public *alertsGenerator(
- numAlerts: number,
- alertAncestors?: number,
- childGenerations?: number,
- maxChildrenPerNode?: number,
- relatedEventsPerNode?: number,
- relatedAlertsPerNode?: number,
- percentNodesWithRelated?: number,
- percentTerminated?: number,
- alwaysGenMaxChildrenPerNode?: boolean
- ) {
+ public *alertsGenerator(numAlerts: number, options: TreeOptions = {}) {
for (let i = 0; i < numAlerts; i++) {
- yield* this.fullResolverTreeGenerator(
- alertAncestors,
- childGenerations,
- maxChildrenPerNode,
- relatedEventsPerNode,
- relatedAlertsPerNode,
- percentNodesWithRelated,
- percentTerminated,
- alwaysGenMaxChildrenPerNode
- );
+ yield* this.fullResolverTreeGenerator(options);
}
}
@@ -600,21 +600,12 @@ export class EndpointDocGenerator {
* @param percentTerminated - percent of nodes which will have process termination events
* @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children
*/
- public *fullResolverTreeGenerator(
- alertAncestors?: number,
- childGenerations?: number,
- maxChildrenPerNode?: number,
- relatedEventsPerNode?: RelatedEventInfo[] | number,
- relatedAlertsPerNode?: number,
- percentNodesWithRelated?: number,
- percentTerminated?: number,
- alwaysGenMaxChildrenPerNode?: boolean
- ) {
+ public *fullResolverTreeGenerator(options: TreeOptions = {}) {
const ancestry = this.createAlertEventAncestry(
- alertAncestors,
- relatedEventsPerNode,
- percentNodesWithRelated,
- percentTerminated
+ options.ancestors,
+ options.relatedEvents,
+ options.percentWithRelated,
+ options.percentTerminated
);
for (let i = 0; i < ancestry.length; i++) {
yield ancestry[i];
@@ -622,13 +613,13 @@ export class EndpointDocGenerator {
// ancestry will always have at least 2 elements, and the last element will be the alert
yield* this.descendantsTreeGenerator(
ancestry[ancestry.length - 1],
- childGenerations,
- maxChildrenPerNode,
- relatedEventsPerNode,
- relatedAlertsPerNode,
- percentNodesWithRelated,
- percentTerminated,
- alwaysGenMaxChildrenPerNode
+ options.generations,
+ options.children,
+ options.relatedEvents,
+ options.relatedAlerts,
+ options.percentWithRelated,
+ options.percentTerminated,
+ options.alwaysGenMaxChildrenPerNode
);
}
@@ -940,7 +931,7 @@ export class EndpointDocGenerator {
host: {
id: this.commonInfo.host.id,
},
- endpoint: {
+ Endpoint: {
policy: {
applied: {
actions: [
@@ -1045,7 +1036,7 @@ export class EndpointDocGenerator {
status: HostPolicyResponseActionStatus.success,
},
],
- id: this.commonInfo.endpoint.policy.applied.id,
+ id: this.commonInfo.Endpoint.policy.applied.id,
response: {
configurations: {
events: {
@@ -1086,9 +1077,9 @@ export class EndpointDocGenerator {
],
},
},
- status: this.commonInfo.endpoint.policy.applied.status,
+ status: this.commonInfo.Endpoint.policy.applied.status,
version: policyVersion,
- name: this.commonInfo.endpoint.policy.applied.name,
+ name: this.commonInfo.Endpoint.policy.applied.name,
},
},
},
diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
new file mode 100644
index 000000000000..d868ba63b1ed
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
@@ -0,0 +1,92 @@
+/*
+ * 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 { Client } from '@elastic/elasticsearch';
+import seedrandom from 'seedrandom';
+import { EndpointDocGenerator, TreeOptions, Event } from './generate_data';
+
+export async function indexHostsAndAlerts(
+ client: Client,
+ seed: string,
+ numHosts: number,
+ numDocs: number,
+ metadataIndex: string,
+ policyIndex: string,
+ eventIndex: string,
+ alertsPerHost: number,
+ options: TreeOptions = {}
+) {
+ const random = seedrandom(seed);
+ for (let i = 0; i < numHosts; i++) {
+ const generator = new EndpointDocGenerator(random);
+ await indexHostDocs(numDocs, client, metadataIndex, policyIndex, generator);
+ await indexAlerts(client, eventIndex, generator, alertsPerHost, options);
+ }
+ await client.indices.refresh({
+ index: eventIndex,
+ });
+ // TODO: Unclear why the documents are not showing up after the call to refresh.
+ // Waiting 5 seconds allows the indices to refresh automatically and
+ // the documents become available in API/integration tests.
+ await delay(5000);
+}
+
+function delay(ms: number) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+async function indexHostDocs(
+ numDocs: number,
+ client: Client,
+ metadataIndex: string,
+ policyIndex: string,
+ generator: EndpointDocGenerator
+) {
+ const timeBetweenDocs = 6 * 3600 * 1000; // 6 hours between metadata documents
+ const timestamp = new Date().getTime();
+ for (let j = 0; j < numDocs; j++) {
+ generator.updateHostData();
+ generator.updatePolicyId();
+ await client.index({
+ index: metadataIndex,
+ body: generator.generateHostMetadata(timestamp - timeBetweenDocs * (numDocs - j - 1)),
+ op_type: 'create',
+ });
+ await client.index({
+ index: policyIndex,
+ body: generator.generatePolicyResponse(timestamp - timeBetweenDocs * (numDocs - j - 1)),
+ op_type: 'create',
+ });
+ }
+}
+
+async function indexAlerts(
+ client: Client,
+ index: string,
+ generator: EndpointDocGenerator,
+ numAlerts: number,
+ options: TreeOptions = {}
+) {
+ const alertGenerator = generator.alertsGenerator(numAlerts, options);
+ let result = alertGenerator.next();
+ while (!result.done) {
+ let k = 0;
+ const resolverDocs: Event[] = [];
+ while (k < 1000 && !result.done) {
+ resolverDocs.push(result.value);
+ result = alertGenerator.next();
+ k++;
+ }
+ const body = resolverDocs.reduce(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (array: Array>, doc) => (
+ array.push({ create: { _index: index } }, doc), array
+ ),
+ []
+ );
+ await client.bulk({ body, refresh: 'true' });
+ }
+}
diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts
index 0341b7593caf..bd4d8372497f 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types.ts
@@ -173,12 +173,19 @@ export interface HostResultList {
}
/**
- * Operating System metadata for a host.
+ * Operating System metadata.
*/
-export interface HostOS {
+export interface OSFields {
full: string;
name: string;
version: string;
+ Ext: OSFieldsExt;
+}
+
+/**
+ * Extended Operating System metadata.
+ */
+export interface OSFieldsExt {
variant: string;
}
@@ -190,7 +197,7 @@ export interface Host {
hostname: string;
ip: string[];
mac: string[];
- os: HostOS;
+ os: OSFields;
}
/**
@@ -220,27 +227,30 @@ interface MalwareClassification {
interface ThreadFields {
id: number;
- service_name: string;
- start: number;
- start_address: number;
- start_address_module: string;
+ Ext: {
+ service_name: string;
+ start: number;
+ start_address: number;
+ start_address_module: string;
+ };
}
interface DllFields {
+ hash: Hashes;
+ path: string;
pe: {
architecture: string;
- imphash: string;
};
code_signature: {
subject_name: string;
trusted: boolean;
};
- compile_time: number;
- hash: Hashes;
- malware_classification: MalwareClassification;
- mapped_address: number;
- mapped_size: number;
- path: string;
+ Ext: {
+ compile_time: number;
+ malware_classification: MalwareClassification;
+ mapped_address: number;
+ mapped_size: number;
+ };
}
/**
@@ -265,7 +275,7 @@ export interface AlertEvent {
module: string;
type: string;
};
- endpoint: {
+ Endpoint: {
policy: {
applied: {
id: string;
@@ -275,12 +285,7 @@ export interface AlertEvent {
};
};
process: {
- code_signature: {
- subject_name: string;
- trusted: boolean;
- };
command_line?: string;
- domain?: string;
pid: number;
ppid?: number;
entity_id: string;
@@ -290,29 +295,31 @@ export interface AlertEvent {
};
name: string;
hash: Hashes;
- pe?: {
- imphash: string;
- };
executable: string;
- sid?: string;
start: number;
- malware_classification?: MalwareClassification;
- token: {
- domain: string;
- type: string;
- user: string;
- sid: string;
- integrity_level: number;
- integrity_level_name: string;
- privileges?: Array<{
- description: string;
- name: string;
- enabled: boolean;
- }>;
- };
thread?: ThreadFields[];
uptime: number;
- user: string;
+ Ext: {
+ code_signature: Array<{
+ subject_name: string;
+ trusted: boolean;
+ }>;
+ malware_classification?: MalwareClassification;
+ token: {
+ domain: string;
+ type: string;
+ user: string;
+ sid: string;
+ integrity_level: number;
+ integrity_level_name: string;
+ privileges?: Array<{
+ description: string;
+ name: string;
+ enabled: boolean;
+ }>;
+ };
+ user: string;
+ };
};
file: {
owner: string;
@@ -323,15 +330,14 @@ export interface AlertEvent {
created: number;
size: number;
hash: Hashes;
- pe?: {
- imphash: string;
- };
- code_signature: {
- trusted: boolean;
- subject_name: string;
+ Ext: {
+ malware_classification: MalwareClassification;
+ temp_file_path: string;
+ code_signature: Array<{
+ trusted: boolean;
+ subject_name: string;
+ }>;
};
- malware_classification: MalwareClassification;
- temp_file_path: string;
};
host: Host;
dll?: DllFields[];
@@ -373,7 +379,7 @@ export type HostMetadata = Immutable<{
id: string;
};
};
- endpoint: {
+ Endpoint: {
policy: {
applied: {
id: string;
@@ -666,7 +672,7 @@ export interface HostPolicyResponseAppliedAction {
message: string;
}
-export type HostPolicyResponseConfiguration = HostPolicyResponse['endpoint']['policy']['applied']['response']['configurations'];
+export type HostPolicyResponseConfiguration = HostPolicyResponse['Endpoint']['policy']['applied']['response']['configurations'];
interface HostPolicyResponseConfigurationStatus {
status: HostPolicyResponseActionStatus;
@@ -711,7 +717,7 @@ export interface HostPolicyResponse {
version: string;
id: string;
};
- endpoint: {
+ Endpoint: {
policy: {
applied: {
version: string;
diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/metadata/file_accordion.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/metadata/file_accordion.tsx
index 6c9df46c9313..82800c92e742 100644
--- a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/metadata/file_accordion.tsx
+++ b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/metadata/file_accordion.tsx
@@ -74,7 +74,7 @@ export const FileAccordion = memo(({ alertData }: { alertData: Immutable {
} else if (columnId === 'archived') {
return null;
} else if (columnId === 'malware_score') {
- return row.file.malware_classification.score;
+ return row.file.Ext.malware_classification.score;
}
return null;
},
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
index 5e7cbc0ef58d..20365b3fe100 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
@@ -41,7 +41,7 @@ export const detailsError = (state: Immutable) => state.detailsError;
* Returns the full policy response from the endpoint after a user modifies a policy.
*/
const detailsPolicyAppliedResponse = (state: Immutable) =>
- state.policyResponse && state.policyResponse.endpoint.policy.applied;
+ state.policyResponse && state.policyResponse.Endpoint.policy.applied;
/**
* Returns the response configurations from the endpoint after a user modifies a policy.
@@ -179,6 +179,6 @@ export const showView: (state: HostState) => 'policy_response' | 'details' = cre
export const policyResponseStatus: (state: Immutable) => string = createSelector(
(state) => state.policyResponse,
(policyResponse) => {
- return (policyResponse && policyResponse?.endpoint?.policy?.applied?.status) || '';
+ return (policyResponse && policyResponse?.Endpoint?.policy?.applied?.status) || '';
}
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
index 9ec65a5d1789..f31b54b93851 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
@@ -97,15 +97,15 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
return [
getManagementUrl({
name: 'policyDetails',
- policyId: details.endpoint.policy.applied.id,
+ policyId: details.Endpoint.policy.applied.id,
excludePrefix: true,
}),
getManagementUrl({
name: 'policyDetails',
- policyId: details.endpoint.policy.applied.id,
+ policyId: details.Endpoint.policy.applied.id,
}),
];
- }, [details.endpoint.policy.applied.id]);
+ }, [details.Endpoint.policy.applied.id]);
const policyDetailsClickHandler = useNavigateByRouterEventHandler(policyDetailsRoutePath);
@@ -123,7 +123,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
href={policyDetailsRouteUrl}
onClick={policyDetailsClickHandler}
>
- {details.endpoint.policy.applied.name}
+ {details.Endpoint.policy.applied.name}
>
),
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 62a9466c2847..68c4e9607418 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -50,13 +50,13 @@ describe('when on the hosts page', () => {
});
describe('when list data loads', () => {
const generatedPolicyStatuses: Array<
- HostInfo['metadata']['endpoint']['policy']['applied']['status']
+ HostInfo['metadata']['Endpoint']['policy']['applied']['status']
> = [];
let firstPolicyID: string;
beforeEach(() => {
reactTestingLibrary.act(() => {
const hostListData = mockHostResultList({ total: 3 });
- firstPolicyID = hostListData.hosts[0].metadata.endpoint.policy.applied.id;
+ firstPolicyID = hostListData.hosts[0].metadata.Endpoint.policy.applied.id;
[HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE].forEach((status, index) => {
hostListData.hosts[index] = {
metadata: hostListData.hosts[index].metadata,
@@ -64,7 +64,7 @@ describe('when on the hosts page', () => {
};
});
hostListData.hosts.forEach((item, index) => {
- generatedPolicyStatuses[index] = item.metadata.endpoint.policy.applied.status;
+ generatedPolicyStatuses[index] = item.metadata.Endpoint.policy.applied.status;
});
const action: AppAction = {
type: 'serverReturnedHostList',
@@ -160,9 +160,11 @@ describe('when on the hosts page', () => {
overallStatus: HostPolicyResponseActionStatus = HostPolicyResponseActionStatus.success
) => {
const policyResponse = docGenerator.generatePolicyResponse();
- policyResponse.endpoint.policy.applied.status = overallStatus;
- policyResponse.endpoint.policy.applied.response.configurations.malware.status = overallStatus;
- let downloadModelAction = policyResponse.endpoint.policy.applied.actions.find(
+ const malwareResponseConfigurations =
+ policyResponse.Endpoint.policy.applied.response.configurations.malware;
+ policyResponse.Endpoint.policy.applied.status = overallStatus;
+ malwareResponseConfigurations.status = overallStatus;
+ let downloadModelAction = policyResponse.Endpoint.policy.applied.actions.find(
(action) => action.name === 'download_model'
);
@@ -172,19 +174,30 @@ describe('when on the hosts page', () => {
message: 'Failed to apply a portion of the configuration (kernel)',
status: overallStatus,
};
- policyResponse.endpoint.policy.applied.actions.push(downloadModelAction);
+ policyResponse.Endpoint.policy.applied.actions.push(downloadModelAction);
}
+
if (
overallStatus === HostPolicyResponseActionStatus.failure ||
overallStatus === HostPolicyResponseActionStatus.warning
) {
downloadModelAction.message = 'no action taken';
}
- store.dispatch({
- type: 'serverReturnedHostPolicyResponse',
- payload: {
- policy_response: policyResponse,
- },
+
+ // Make sure that at least one configuration has the above action, else
+ // we get into an out-of-sync condition
+ if (
+ malwareResponseConfigurations.concerned_actions.indexOf(downloadModelAction.name) === -1
+ ) {
+ malwareResponseConfigurations.concerned_actions.push(downloadModelAction.name);
+ }
+ reactTestingLibrary.act(() => {
+ store.dispatch({
+ type: 'serverReturnedHostPolicyResponse',
+ payload: {
+ policy_response: policyResponse,
+ },
+ });
});
};
@@ -236,7 +249,7 @@ describe('when on the hosts page', () => {
const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue');
expect(policyDetailsLink).not.toBeNull();
expect(policyDetailsLink.getAttribute('href')).toEqual(
- `#/management/policy/${hostDetails.metadata.endpoint.policy.applied.id}`
+ `#/management/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
);
});
@@ -252,7 +265,7 @@ describe('when on the hosts page', () => {
});
const changedUrlAction = await userChangedUrlChecker;
expect(changedUrlAction.payload.pathname).toEqual(
- `/management/policy/${hostDetails.metadata.endpoint.policy.applied.id}`
+ `/management/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
);
});
@@ -361,6 +374,12 @@ describe('when on the hosts page', () => {
describe.skip('when showing host Policy Response panel', () => {
let renderResult: ReturnType;
beforeEach(async () => {
+ coreStart.http.post.mockImplementation(async (requestOptions) => {
+ if (requestOptions.path === '/api/endpoint/metadata') {
+ return mockHostResultList({ total: 0 });
+ }
+ throw new Error(`POST to '${requestOptions.path}' does not have a mock response!`);
+ });
renderResult = render();
const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl');
@@ -373,6 +392,8 @@ describe('when on the hosts page', () => {
});
});
+ afterEach(reactTestingLibrary.cleanup);
+
it('should hide the host details panel', async () => {
const hostDetailsFlyout = await renderResult.queryByTestId('hostDetailsFlyoutBody');
expect(hostDetailsFlyout).toBeNull();
@@ -433,21 +454,29 @@ describe('when on the hosts page', () => {
});
});
- it('should show a numbered badge if at least one action failed', () => {
+ it('should show a numbered badge if at least one action failed', async () => {
+ const policyResponseActionDispatched = middlewareSpy.waitForAction(
+ 'serverReturnedHostPolicyResponse'
+ );
reactTestingLibrary.act(() => {
dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure);
});
- const attentionBadge = renderResult.findAllByTestId(
+ await policyResponseActionDispatched;
+ const attentionBadge = await renderResult.findAllByTestId(
'hostDetailsPolicyResponseAttentionBadge'
);
expect(attentionBadge).not.toBeNull();
});
- it('should show a numbered badge if at least one action has a warning', () => {
+ it('should show a numbered badge if at least one action has a warning', async () => {
+ const policyResponseActionDispatched = middlewareSpy.waitForAction(
+ 'serverReturnedHostPolicyResponse'
+ );
reactTestingLibrary.act(() => {
dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning);
});
- const attentionBadge = renderResult.findAllByTestId(
+ await policyResponseActionDispatched;
+ const attentionBadge = await renderResult.findAllByTestId(
'hostDetailsPolicyResponseAttentionBadge'
);
expect(attentionBadge).not.toBeNull();
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index c67c29fbc73a..149819480855 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -154,13 +154,13 @@ export const HostList = () => {
},
},
{
- field: 'metadata.endpoint.policy.applied',
+ field: 'metadata.Endpoint.policy.applied',
name: i18n.translate('xpack.securitySolution.endpointList.policy', {
defaultMessage: 'Policy',
}),
truncateText: true,
// eslint-disable-next-line react/display-name
- render: (policy: HostInfo['metadata']['endpoint']['policy']['applied']) => {
+ render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied']) => {
const toRoutePath = getManagementUrl({
name: 'policyDetails',
policyId: policy.id,
@@ -181,12 +181,12 @@ export const HostList = () => {
},
},
{
- field: 'metadata.endpoint.policy.applied',
+ field: 'metadata.Endpoint.policy.applied',
name: i18n.translate('xpack.securitySolution.endpointList.policyStatus', {
defaultMessage: 'Policy Status',
}),
// eslint-disable-next-line react/display-name
- render: (policy: HostInfo['metadata']['endpoint']['policy']['applied'], item: HostInfo) => {
+ render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied'], item: HostInfo) => {
const toRoutePath = getManagementUrl({
name: 'endpointPolicyResponse',
selected_host: item.metadata.host.id,
diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts
index e4460b337960..542991a927f7 100644
--- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts
+++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts
@@ -8,10 +8,9 @@ import * as path from 'path';
import yargs from 'yargs';
import * as url from 'url';
import fetch from 'node-fetch';
-import seedrandom from 'seedrandom';
import { Client, ClientOptions } from '@elastic/elasticsearch';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
-import { EndpointDocGenerator, Event } from '../../common/endpoint/generate_data';
+import { indexHostsAndAlerts } from '../../common/endpoint/index_data';
main();
@@ -201,59 +200,26 @@ async function main() {
seed = Math.random().toString();
console.log(`No seed supplied, using random seed: ${seed}`);
}
- const random = seedrandom(seed);
const startTime = new Date().getTime();
- for (let i = 0; i < argv.numHosts; i++) {
- const generator = new EndpointDocGenerator(random);
- const timeBetweenDocs = 6 * 3600 * 1000; // 6 hours between metadata documents
-
- const timestamp = new Date().getTime();
- for (let j = 0; j < argv.numDocs; j++) {
- generator.updateHostData();
- generator.updatePolicyId();
- await client.index({
- index: argv.metadataIndex,
- body: generator.generateHostMetadata(timestamp - timeBetweenDocs * (argv.numDocs - j - 1)),
- op_type: 'create',
- });
- await client.index({
- index: argv.policyIndex,
- body: generator.generatePolicyResponse(
- timestamp - timeBetweenDocs * (argv.numDocs - j - 1)
- ),
- op_type: 'create',
- });
+ await indexHostsAndAlerts(
+ client,
+ seed,
+ argv.numHosts,
+ argv.numDocs,
+ argv.metadataIndex,
+ argv.policyIndex,
+ argv.eventIndex,
+ argv.alertsPerHost,
+ {
+ ancestors: argv.ancestors,
+ generations: argv.generations,
+ children: argv.children,
+ relatedEvents: argv.relatedEvents,
+ relatedAlerts: argv.relatedAlerts,
+ percentWithRelated: argv.percentWithRelated,
+ percentTerminated: argv.percentTerminated,
+ alwaysGenMaxChildrenPerNode: argv.maxChildrenPerNode,
}
-
- const alertGenerator = generator.alertsGenerator(
- argv.alertsPerHost,
- argv.ancestors,
- argv.generations,
- argv.children,
- argv.relatedEvents,
- argv.relatedAlerts,
- argv.percentWithRelated,
- argv.percentTerminated,
- argv.maxChildrenPerNode
- );
- let result = alertGenerator.next();
- while (!result.done) {
- let k = 0;
- const resolverDocs: Event[] = [];
- while (k < 1000 && !result.done) {
- resolverDocs.push(result.value);
- result = alertGenerator.next();
- k++;
- }
- const body = resolverDocs.reduce(
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (array: Array>, doc) => (
- array.push({ create: { _index: argv.eventIndex } }, doc), array
- ),
- []
- );
- await client.bulk({ body, refresh: 'true' });
- }
- }
+ );
console.log(`Creating and indexing documents took: ${new Date().getTime() - startTime}ms`);
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
index 80626bbdb6e7..92835dc5329c 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
@@ -237,7 +237,7 @@ describe('test endpoint route', () => {
expect(routeConfig.options).toEqual({ authRequired: true });
expect(mockResponse.ok).toBeCalled();
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
- expect(result).toHaveProperty('metadata.endpoint');
+ expect(result).toHaveProperty('metadata.Endpoint');
expect(result.host_status).toEqual(HostStatus.ONLINE);
});
diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
index 12c5857f9db3..ed287834761d 100644
--- a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
+++ b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
@@ -7,12 +7,19 @@ import expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { AlertData } from '../../../../../plugins/security_solution/common/endpoint_alerts/types';
import { eventsIndexPattern } from '../../../../../plugins/security_solution/common/endpoint/constants';
-import { deleteEventsStream, deleteMetadataStream } from '../data_stream_helper';
+import {
+ deleteEventsStream,
+ deleteMetadataStream,
+ deletePolicyStream,
+} from '../data_stream_helper';
+import { indexHostsAndAlerts } from '../../../../../plugins/security_solution/common/endpoint/index_data';
/**
* The number of alert documents in the es archive.
*/
-const numberOfAlertsInFixture = 12;
+const numberOfHosts = 3;
+const numberOfAlertsPerHost = 4;
+const numberOfAlertsInFixture = numberOfHosts * numberOfAlertsPerHost;
/**
* The default number of entries returned when no page_size is specified.
@@ -57,10 +64,9 @@ const ES_QUERY_MISSING = {
};
export default function ({ getService }: FtrProviderContext) {
- const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const es = getService('legacyEs');
-
+ const client = getService('es');
const nextPrevPrefixQuery = "query=(language:kuery,query:'')";
const nextPrevPrefixDateRange = "date_range=(from:'2018-01-10T00:00:00.000Z',to:now)";
const nextPrevPrefixSort = 'sort=@timestamp';
@@ -73,8 +79,17 @@ export default function ({ getService }: FtrProviderContext) {
describe('Endpoint alert API', () => {
describe('when data is in elasticsearch', () => {
before(async () => {
- await esArchiver.load('endpoint/alerts/api_feature', { useCreate: true });
- await esArchiver.load('endpoint/alerts/host_api_feature', { useCreate: true });
+ await indexHostsAndAlerts(
+ client,
+ 'alerts-seed',
+ numberOfHosts,
+ 1,
+ 'metrics-endpoint.metadata-default-1',
+ 'metrics-endpoint.policy-default-1',
+ 'events-endpoint-1',
+ numberOfAlertsPerHost
+ );
+
const res = await es.search({
index: eventsIndexPattern,
body: ES_QUERY_MISSING,
@@ -85,7 +100,11 @@ export default function ({ getService }: FtrProviderContext) {
after(async () => {
// the endpoint uses data streams and es archiver does not support deleting them at the moment so we need
// to do it manually
- await Promise.all([deleteEventsStream(getService), deleteMetadataStream(getService)]);
+ await Promise.all([
+ deleteEventsStream(getService),
+ deleteMetadataStream(getService),
+ deletePolicyStream(getService),
+ ]);
});
it('should not support POST requests', async () => {
@@ -159,7 +178,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(body.message).to.contain('Value must be equal to or greater than [1]');
});
- it('should return links to the next and previous pages using cursor-based pagination', async () => {
+ it('should return working link to the next page using cursor-based pagination', async () => {
const { body } = await supertest
.get('/api/endpoint/alerts?page_index=0')
.set('kbn-xsrf', 'xxx')
@@ -170,51 +189,75 @@ export default function ({ getService }: FtrProviderContext) {
expect(body.next).to.eql(
`/api/endpoint/alerts?${nextPrevPrefix}&after=${lastTimestampFirstPage}&after=${lastEventIdFirstPage}`
);
- });
- it('should return data using `next` link', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefix}&after=1584044338719&after=66008e21-2493-4b15-a937-939ea228064a`
- )
+ const { body: nextBody } = await supertest
+ .get(body.next)
.set('kbn-xsrf', 'xxx')
.expect(200);
- expect(body.alerts.length).to.eql(defaultPageSize);
- const firstTimestampNextPage = body.alerts[0]['@timestamp'];
- const firstEventIdNextPage = body.alerts[0].event.id;
- expect(body.prev).to.eql(
+ expect(nextBody.alerts.length).to.eql(2);
+ const firstTimestampNextPage = nextBody.alerts[0]['@timestamp'];
+ const firstEventIdNextPage = nextBody.alerts[0].event.id;
+ expect(nextBody.prev).to.eql(
`/api/endpoint/alerts?${nextPrevPrefix}&before=${firstTimestampNextPage}&before=${firstEventIdNextPage}`
);
});
- it('should return data using `prev` link', async () => {
+ it('should return working link to the prev page using cursor-based pagination', async () => {
const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefix}&before=1542789412000&before=823d814d-fa0c-4e53-a94c-f6b296bb965b`
- )
+ .get('/api/endpoint/alerts?page_index=1')
.set('kbn-xsrf', 'xxx')
.expect(200);
- expect(body.alerts.length).to.eql(10);
+ expect(body.alerts.length).to.eql(2);
+ const firstTimestamp = body.alerts[0]['@timestamp'];
+ const firstEventId = body.alerts[0].event.id;
+ expect(body.prev).to.eql(
+ `/api/endpoint/alerts?${nextPrevPrefix}&before=${firstTimestamp}&before=${firstEventId}`
+ );
+
+ const { body: prevBody } = await supertest
+ .get(body.prev)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+ expect(prevBody.alerts.length).to.eql(10);
+ const lastTimestampFirstPage = prevBody.alerts[9]['@timestamp'];
+ const lastEventIdFirstPage = prevBody.alerts[9].event.id;
+ expect(prevBody.next).to.eql(
+ `/api/endpoint/alerts?${nextPrevPrefix}&after=${lastTimestampFirstPage}&after=${lastEventIdFirstPage}`
+ );
});
it('should return no results when `before` is requested past beginning of first page', async () => {
const { body } = await supertest
+ .get('/api/endpoint/alerts')
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ const { body: emptyBody } = await supertest
.get(
- `/api/endpoint/alerts?${nextPrevPrefix}&before=1584044338726&before=5ff1a4ec-758e-49e7-89aa-2c6821fe6b54`
+ `/api/endpoint/alerts?${nextPrevPrefix}&before=${
+ body.alerts[0]['@timestamp'] + 1
+ }&before=5ff1a4ec-758e-49e7-89aa-2c6821fe6b54`
)
.set('kbn-xsrf', 'xxx')
.expect(200);
- expect(body.alerts.length).to.eql(0);
+ expect(emptyBody.alerts.length).to.eql(0);
});
it('should return no results when `after` is requested past end of last page, descending', async () => {
const { body } = await supertest
+ .get('/api/endpoint/alerts?page_index=1')
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ const { body: emptyBody } = await supertest
.get(
- `/api/endpoint/alerts?${nextPrevPrefix}&after=1584044338612&after=6d75d498-3cca-45ad-a304-525b95ae0412`
+ `/api/endpoint/alerts?${nextPrevPrefix}&after=${
+ body.alerts[1]['@timestamp'] - 1
+ }&after=6d75d498-3cca-45ad-a304-525b95ae0412`
)
.set('kbn-xsrf', 'xxx')
.expect(200);
- expect(body.alerts.length).to.eql(0);
+ expect(emptyBody.alerts.length).to.eql(0);
});
it('alerts api should return data using `before` by custom sort parameter, descending', async () => {
@@ -346,7 +389,12 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should filter results of alert data using rison-encoded filters', async () => {
- const hostname = 'Host-abmfhmc5ku';
+ const { body: firstBody } = await supertest
+ .get('/api/endpoint/alerts?page_index=0')
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ const hostname = firstBody.alerts[0].host.hostname;
const { body } = await supertest
.get(
`/api/endpoint/alerts?filters=!((%27%24state%27%3A(store%3AappState)%2Cmeta%3A(alias%3A!n%2Cdisabled%3A!f%2Ckey%3Ahost.hostname%2Cnegate%3A!f%2Cparams%3A(query%3A${hostname})%2Ctype%3Aphrase)%2Cquery%3A(match_phrase%3A(host.hostname%3A${hostname}))))`
@@ -361,7 +409,12 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should filter results of alert data using KQL', async () => {
- const agentID = '7cf9f7a3-28a6-4d1e-bb45-005aa28f18d0';
+ const { body: firstBody } = await supertest
+ .get('/api/endpoint/alerts?page_index=0')
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ const agentID = firstBody.alerts[0].agent.id;
const { body } = await supertest
.get(
`/api/endpoint/alerts?query=(language%3Akuery%2Cquery%3A%27agent.id%20%3A%20"${agentID}"%27)`
diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/api_integration/apis/endpoint/metadata.ts
index 61f294cbd6f9..41531269ddeb 100644
--- a/x-pack/test/api_integration/apis/endpoint/metadata.ts
+++ b/x-pack/test/api_integration/apis/endpoint/metadata.ts
@@ -160,18 +160,18 @@ export default function ({ getService }: FtrProviderContext) {
expect(body.request_page_index).to.eql(0);
});
- it('metadata api should return page based on host.os.variant filter.', async () => {
+ it('metadata api should return page based on host.os.Ext.variant filter.', async () => {
const variantValue = 'Windows Pro';
const { body } = await supertest
.post('/api/endpoint/metadata')
.set('kbn-xsrf', 'xxx')
.send({
- filter: `host.os.variant:${variantValue}`,
+ filter: `host.os.Ext.variant:${variantValue}`,
})
.expect(200);
expect(body.total).to.eql(2);
const resultOsVariantValue: Set = new Set(
- body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.os.variant)
+ body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.os.Ext.variant)
);
expect(Array.from(resultOsVariantValue)).to.eql([variantValue]);
expect(body.hosts.length).to.eql(2);
@@ -204,15 +204,14 @@ export default function ({ getService }: FtrProviderContext) {
.post('/api/endpoint/metadata')
.set('kbn-xsrf', 'xxx')
.send({
- filter: `not endpoint.policy.applied.status:success`,
+ filter: `not Endpoint.policy.applied.status:success`,
})
.expect(200);
const statuses: Set = new Set(
body.hosts.map(
- (hostInfo: Record) => hostInfo.metadata.endpoint.policy.applied.status
+ (hostInfo: Record) => hostInfo.metadata.Endpoint.policy.applied.status
)
);
-
expect(statuses.size).to.eql(1);
expect(Array.from(statuses)).to.eql(['failure']);
});
diff --git a/x-pack/test/api_integration/apis/endpoint/policy.ts b/x-pack/test/api_integration/apis/endpoint/policy.ts
index 711762cc20ab..e33423d17256 100644
--- a/x-pack/test/api_integration/apis/endpoint/policy.ts
+++ b/x-pack/test/api_integration/apis/endpoint/policy.ts
@@ -27,7 +27,7 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
expect(body.policy_response.host.id).to.eql(expectedHostId);
- expect(body.policy_response.endpoint.policy).to.not.be(undefined);
+ expect(body.policy_response.Endpoint.policy).to.not.be(undefined);
});
it('should return not found if host has no policy response', async () => {
diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
index a8d868ebbec1..1434aae60c96 100644
--- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
+++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
@@ -15,7 +15,7 @@
"id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -44,7 +44,9 @@
"full": "Windows 10",
"name": "windows 10.0",
"version": "10.0",
- "variant" : "Windows Pro"
+ "Ext": {
+ "variant" : "Windows Pro"
+ }
}
}
}
@@ -68,7 +70,7 @@
"id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -96,7 +98,9 @@
"full": "Windows Server 2016",
"name": "windows 10.0",
"version": "10.0",
- "variant" : "Windows Server"
+ "Ext": {
+ "variant" : "Windows Server"
+ }
}
}
}
@@ -120,7 +124,7 @@
"id": "023fa40c-411d-4188-a941-4147bfadd095"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -146,7 +150,9 @@
"full": "Windows 10",
"name": "windows 10.0",
"version": "10.0",
- "variant" : "Windows Pro"
+ "Ext": {
+ "variant" : "Windows Pro"
+ }
}
}
}
@@ -170,7 +176,7 @@
"id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -199,7 +205,9 @@
"full": "Windows Server 2016",
"name": "windows 10.0",
"version": "10.0",
- "variant" : "Windows Server 2016"
+ "Ext": {
+ "variant" : "Windows Server 2016"
+ }
}
}
}
@@ -223,7 +231,7 @@
"id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -250,7 +258,9 @@
"full": "Windows Server 2012",
"name": "windows 6.2",
"version": "6.2",
- "variant" : "Windows Server 2012"
+ "Ext": {
+ "variant" : "Windows Server 2012"
+ }
}
}
}
@@ -274,7 +284,7 @@
"id": "023fa40c-411d-4188-a941-4147bfadd095"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "With Eventing",
@@ -301,7 +311,9 @@
"full": "Windows Server 2012",
"name": "windows 6.2",
"version": "6.2",
- "variant" : "Windows Server 2012"
+ "Ext": {
+ "variant" : "Windows Server 2012"
+ }
}
}
}
@@ -325,7 +337,7 @@
"id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "With Eventing",
@@ -353,7 +365,9 @@
"full": "Windows Server 2012R2",
"name": "windows 6.3",
"version": "6.3",
- "variant" : "Windows Server 2012 R2"
+ "Ext": {
+ "variant" : "Windows Server 2012 R2"
+ }
}
}
}
@@ -377,7 +391,7 @@
"id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "Default",
@@ -404,7 +418,9 @@
"full": "Windows Server 2012R2",
"name": "windows 6.3",
"version": "6.3",
- "variant" : "Windows Server 2012 R2"
+ "Ext": {
+ "variant" : "Windows Server 2012 R2"
+ }
}
}
}
@@ -428,7 +444,7 @@
"id": "023fa40c-411d-4188-a941-4147bfadd095"
}
},
- "endpoint": {
+ "Endpoint": {
"policy": {
"applied": {
"name": "With Eventing",
@@ -455,7 +471,9 @@
"full": "Windows Server 2012",
"name": "windows 6.2",
"version": "6.2",
- "variant" : "Windows Server 2012"
+ "Ext": {
+ "variant" : "Windows Server 2012"
+ }
}
}
}
diff --git a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz
index f380785f021bbfa49f17206df174a6a44c9d69df..d9fcf03f43f37d02e93a8c20bf8a1ba3d33ee9ce 100644
GIT binary patch
delta 1297
zcmV+s1@8K;3a<(WABzYGXDsTG2Qhyv3X&Xec9HL1QZMTn+kw2@gDyc#%?y7W4qyLo
zHvlZM=a?;i0t+h0;#M_0;c%~7zR4f*C#UM`hx^a5Zmxg*`}y~ePre=g_VM#WZmujM
z%DyP09m^6PD&9;w(T
zN3%`SzO78~mPJ{^r)I;thKX&t5Su=MNd6B!gfO&S>iJYB7Pi>p_UQG*l9Y=`x#~-8
zsliYX94-yb&@`mmOKnlwzSMm)lD2zp#Xy5DAx<+++HUu-NuCcWKZF$3KmaU6%7(N44aKr%V1wD&=NA|eX-cAv0K
zIkJ@Dggk6yd&F|L0WYA6B2DG6)eu{bfQa2L1$9^o(mf&4IM|2bb7Q{hNU}V#y<%IG
zmWz(fOV_CZ{m7Dtg(ng3F%Kn5CXQgBbbXa5kbctk5~5ISw>;XOMEZZDk(Oq-Vis-KlU(3K>*p21G!tMVN(805JU5fA~$9$38@;k@x(v2(ni2v*LC
zysVT{y(zl-!IAUao=Ms~h(U~#RART_rerEliZkbZou4$6r7VBRH+6N^)TQ_{hy;+=
zS5B+*r)ZaFfTRm$_9p^QnA(%N*PwG!+gkQ23|MzAMboFbpuMP8`!wx*M{psbf>N0YK0JV?@VKbbacORR-Mk}tfaAg
zQ4L+E7cKmeQ}~tZL=l0gLDS9g<@V2jVdntoC#c19d;<*F{X68`$glK^)Sabg)hmP?
zYWxj!+3-g1j3Z+mID1AcN3YG-PR?r&u{R~BKZNfvL17&;E72pJBw
zsNEFu9(jM1`xPSBrrv6MOb;R*R1Q&K`kt$lL(irVDd7#9h7AobbSlnSJDs)D0mIG#FkmsM1U8){Iw5-up^piLh%y7ZmhD5w
z4|HrI!|`?PJ8Pp|3a$zVABzYG;-lV?2Qhyn3X*JZcH!?{QV(0!Beny1y9Zr@n3@^>X88Vp
zy8~dBza(t-6PQs!X7{RLi^Gj-`6hqJpPZ_%A09s^y1D%I@0Z^{Zhbra?c?XC!dzLz
zlzmY~5zA9fGRR_@2p;G2M1-8YK*|EV33I5|9+}vrY#To)nJ
z(QMPS?`sphW^vx|Ij~_}!^E~+h)thBB>#sVLKxaE^?a%m3tMb%z-CS|KC
zvy9=CJgsDV%nG+2FQ6txmdUhLiYJbMh$zayAuI*ih7f5SY{KxRFJXAvJU4<$-2j$oj2eU&JXe$w_DqEM{YJYJtg`lEkQmS(wX|FJ6-
z;c$mjc9!c?%)TUYrnVOT**Fp
zAb0$0CO{nnEdN@9NYHcp;`MN4(94O`S@r@jC~75QAOes-vBPGBi^g~5&hdUESUD#0
zvQiE8s_g0qM=ol6A!+j<1_@3xiG2b$CR24%TsZE_;-sN0V`+b}sjIW*Fcp6Wu>kV=
z%4vQ6lxEOEYBwq9
zPNdI4D0V;Aj(LCby9Hf6(H@M2r+MZQE)Ca%=s{b+#R4JdKA6S=>F9CcELxq*QOOed
zqUyU&&ra|MPT^Os6J-RVlBTQU%k7^Y!`1=NEzk+i@eR;p_ivwbqrTEFR(F<;Rc9g;
zP{VJa>(U#%GY;h1bM_2a4qlsYU7Xh*Vs9!=H-+zz^1y#1uRrB1mzz*D&Wz~J=Cjj&
z@TFhdHD95l4*ylVQFk+I88p->4`E#+%!8gz42W#chpu5VXfVy8f$1RMcG~VSj*9*F
zPo6)4r5gDb^bxN|huU^zIwA)(FXX0PW$!5D`G(^$Y(cD343S}(5EBDK$H5dK!=V
z-Tv-Q#ky2G4O`mj*Y$_nYNxhkPTJ|Doi-v(+Uca7PTJ`>u#BdW!IdMAEuS7BwK!EDK`d8<3fr#W1lw7U)f(?tj!q{{wPJ;^$W?
F005q4hvEPL
From 8c76d0e1184ee5cf66a48eb291c6f2a4c41ba8c6 Mon Sep 17 00:00:00 2001
From: DeDe Morton
Date: Thu, 18 Jun 2020 16:00:33 -0700
Subject: [PATCH 09/33] [DOCS] Add related link to the ingest management docs
(#69467) (#69599)
* [DOCS] Add related link to the ingest management docs
* Add link to ingest manager topic in Kibana
* Remove link to ingest manager topic in kibana
---
docs/settings/ingest-manager-settings.asciidoc | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/settings/ingest-manager-settings.asciidoc b/docs/settings/ingest-manager-settings.asciidoc
index 0c3427b034ae..f46c76907904 100644
--- a/docs/settings/ingest-manager-settings.asciidoc
+++ b/docs/settings/ingest-manager-settings.asciidoc
@@ -8,8 +8,10 @@
experimental[]
You can configure `xpack.ingestManager` settings in your `kibana.yml`.
-By default, {ingest-manager} is not enabled. You need to enable it. To use
-{fleet}, you also need to configure {kib} and {es} hosts.
+By default, {ingest-manager} is not enabled. You need to
+enable it. To use {fleet}, you also need to configure {kib} and {es} hosts.
+
+See the {ingest-guide}/index.html[Ingest Management] docs for more information.
[[general-ingest-manager-settings-kb]]
==== General {ingest-manager} settings
From bcf90aa386875b606b0cf1a45abf5acdebab8a8d Mon Sep 17 00:00:00 2001
From: Aaron Caldwell
Date: Thu, 18 Jun 2020 17:29:50 -0600
Subject: [PATCH 10/33] [7.x] [Maps] Migrate maps client router to react
(#65079) (#69576)
Co-authored-by: Elastic Machine
---
.../sidebar/lib/visualize_url_utils.ts | 2 +-
test/functional/services/common/find.ts | 21 +
x-pack/index.js | 2 -
x-pack/legacy/plugins/maps/index.js | 41 --
.../maps/public/angular/map_controller.js | 686 ------------------
x-pack/legacy/plugins/maps/public/index.scss | 3 -
x-pack/legacy/plugins/maps/public/index.ts | 34 -
x-pack/legacy/plugins/maps/public/legacy.ts | 22 -
x-pack/legacy/plugins/maps/public/plugin.ts | 70 --
x-pack/legacy/plugins/maps/public/routes.js | 108 ---
.../functions/common/saved_map.ts | 3 +-
.../input_type_to_expression/map.test.ts | 2 +-
.../input_type_to_expression/map.ts | 2 +-
x-pack/plugins/maps/common/constants.ts | 2 +-
.../public/angular/listing_ng_wrapper.html | 6 -
x-pack/plugins/maps/public/angular/map.html | 35 -
.../es_search_source/load_index_settings.js | 3 +-
.../connected_components/gis_map/view.js | 2 +-
.../maps/public/embeddable/map_embeddable.tsx | 1 -
x-pack/plugins/maps/public/index.ts | 2 -
.../maps/public/lazy_load_bundle/index.ts | 7 +-
.../public/lazy_load_bundle/lazy/index.ts | 8 +-
.../maps/public/maps_vis_type_alias.js | 2 +-
x-pack/plugins/maps/public/plugin.ts | 33 +-
x-pack/plugins/maps/public/reducers/store.js | 13 +-
.../bootstrap}/get_initial_layers.d.ts | 2 +-
.../bootstrap}/get_initial_layers.js | 27 +-
.../bootstrap}/get_initial_layers.test.js | 18 +-
.../bootstrap}/get_initial_query.js | 4 +-
.../bootstrap}/get_initial_refresh_config.js | 4 +-
.../bootstrap}/get_initial_time_filters.js | 4 +-
.../services/gis_map_saved_object_loader.js | 4 +-
.../bootstrap}/services/saved_gis_map.js | 20 +-
.../maps/public/routing/maps_router.js | 62 ++
.../routing/page_elements/breadcrumbs.js | 60 ++
.../page_elements/top_nav_menu/index.js | 44 ++
.../top_nav_menu/top_nav_menu.js | 279 +++++++
.../routes/list/load_list_and_render.js | 57 ++
.../routes/list/maps_list_view.js} | 71 +-
.../public/routing/routes/maps_app/index.js | 67 ++
.../routes/maps_app/load_map_and_render.js | 58 ++
.../routing/routes/maps_app/maps_app_view.js | 476 ++++++++++++
.../state_syncing/app_state_manager.js | 44 ++
.../public/routing/state_syncing/app_sync.js | 61 ++
.../routing/state_syncing/global_sync.ts | 32 +
.../maps/public/routing/store_operations.js | 11 +
.../maps/server/tutorials/ems/index.ts | 3 +-
.../components/embeddables/embedded_map.tsx | 3 +-
.../embeddables/embedded_map_helpers.tsx | 8 +-
.../line_tool_tip_content.test.tsx | 6 +-
.../map_tool_tip/line_tool_tip_content.tsx | 3 +-
.../embeddables/map_tool_tip/map_tool_tip.tsx | 3 +-
.../point_tool_tip_content.test.tsx | 6 +-
.../map_tool_tip/point_tool_tip_content.tsx | 3 +-
.../network/components/embeddables/types.ts | 3 +-
.../translations/translations/ja-JP.json | 4 -
.../translations/translations/zh-CN.json | 4 -
.../location_map/embeddables/embedded_map.tsx | 6 +-
.../apps/maps/saved_object_management.js | 4 +-
.../test/functional/page_objects/gis_page.js | 9 +-
60 files changed, 1456 insertions(+), 1124 deletions(-)
delete mode 100644 x-pack/legacy/plugins/maps/index.js
delete mode 100644 x-pack/legacy/plugins/maps/public/angular/map_controller.js
delete mode 100644 x-pack/legacy/plugins/maps/public/index.scss
delete mode 100644 x-pack/legacy/plugins/maps/public/index.ts
delete mode 100644 x-pack/legacy/plugins/maps/public/legacy.ts
delete mode 100644 x-pack/legacy/plugins/maps/public/plugin.ts
delete mode 100644 x-pack/legacy/plugins/maps/public/routes.js
delete mode 100644 x-pack/plugins/maps/public/angular/listing_ng_wrapper.html
delete mode 100644 x-pack/plugins/maps/public/angular/map.html
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_layers.d.ts (84%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_layers.js (57%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_layers.test.js (82%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_query.js (84%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_refresh_config.js (86%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/get_initial_time_filters.js (90%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/services/gis_map_saved_object_loader.js (87%)
rename x-pack/plugins/maps/public/{angular => routing/bootstrap}/services/saved_gis_map.js (82%)
create mode 100644 x-pack/plugins/maps/public/routing/maps_router.js
create mode 100644 x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
create mode 100644 x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/index.js
create mode 100644 x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js
create mode 100644 x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.js
rename x-pack/plugins/maps/public/{components/map_listing.js => routing/routes/list/maps_list_view.js} (86%)
create mode 100644 x-pack/plugins/maps/public/routing/routes/maps_app/index.js
create mode 100644 x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.js
create mode 100644 x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
create mode 100644 x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.js
create mode 100644 x-pack/plugins/maps/public/routing/state_syncing/app_sync.js
create mode 100644 x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts
create mode 100644 x-pack/plugins/maps/public/routing/store_operations.js
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts b/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
index d585c5d6f263..d598f28a0ad1 100644
--- a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
+++ b/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
@@ -104,7 +104,7 @@ export function getMapsAppUrl(
return {
app: 'maps',
- path: `#/map?${mapAppParams.toString()}`,
+ path: `/map#?${mapAppParams.toString()}`,
};
}
diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts
index 876fb7369fea..88357814357d 100644
--- a/test/functional/services/common/find.ts
+++ b/test/functional/services/common/find.ts
@@ -162,6 +162,27 @@ export async function FindProvider({ getService }: FtrProviderContext) {
return wrapAll(elements);
}
+ public async allByButtonText(
+ buttonText: string,
+ element: WebDriver | WebElement | WebElementWrapper = driver,
+ timeout: number = defaultFindTimeout
+ ): Promise {
+ log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`);
+ return await retry.tryForTime(timeout, async () => {
+ // tslint:disable-next-line:variable-name
+ const _element = element instanceof WebElementWrapper ? element._webElement : element;
+ await this._withTimeout(0);
+ const allButtons = wrapAll(await _element.findElements(By.tagName('button')));
+ await this._withTimeout(defaultFindTimeout);
+ const buttonTexts = await Promise.all(
+ allButtons.map(async (el) => {
+ return el.getVisibleText();
+ })
+ );
+ return buttonTexts.filter((text) => text.trim() === buttonText.trim());
+ });
+ }
+
public async allByCssSelector(
selector: string,
timeout: number = defaultFindTimeout
diff --git a/x-pack/index.js b/x-pack/index.js
index 8096774d7a49..e7dd4886e605 100644
--- a/x-pack/index.js
+++ b/x-pack/index.js
@@ -9,7 +9,6 @@ import { monitoring } from './legacy/plugins/monitoring';
import { security } from './legacy/plugins/security';
import { dashboardMode } from './legacy/plugins/dashboard_mode';
import { beats } from './legacy/plugins/beats_management';
-import { maps } from './legacy/plugins/maps';
import { spaces } from './legacy/plugins/spaces';
import { ingestManager } from './legacy/plugins/ingest_manager';
@@ -21,7 +20,6 @@ module.exports = function (kibana) {
security(kibana),
dashboardMode(kibana),
beats(kibana),
- maps(kibana),
ingestManager(kibana),
];
};
diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js
deleted file mode 100644
index d1354ef013a9..000000000000
--- a/x-pack/legacy/plugins/maps/index.js
+++ /dev/null
@@ -1,41 +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 { i18n } from '@kbn/i18n';
-import { resolve } from 'path';
-import { getAppTitle } from '../../../plugins/maps/common/i18n_getters';
-import { APP_ID, APP_ICON } from '../../../plugins/maps/common/constants';
-import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
-
-export function maps(kibana) {
- return new kibana.Plugin({
- require: ['kibana', 'elasticsearch'],
- id: APP_ID,
- configPrefix: 'xpack.maps',
- publicDir: resolve(__dirname, 'public'),
- config(Joi) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- })
- .unknown()
- .default();
- },
- uiExports: {
- app: {
- title: getAppTitle(),
- description: i18n.translate('xpack.maps.appDescription', {
- defaultMessage: 'Map application',
- }),
- main: 'plugins/maps/legacy',
- icon: 'plugins/maps/icon.svg',
- euiIconType: APP_ICON,
- category: DEFAULT_APP_CATEGORIES.kibana,
- order: 4000,
- },
- styleSheetPaths: `${__dirname}/public/index.scss`,
- },
- });
-}
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
deleted file mode 100644
index 70d5195feef4..000000000000
--- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js
+++ /dev/null
@@ -1,686 +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 _ from 'lodash';
-import rison from 'rison-node';
-import 'ui/directives/listen';
-import 'ui/directives/storage';
-import React from 'react';
-import { I18nProvider } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { uiModules } from 'ui/modules';
-import {
- getTimeFilter,
- getIndexPatternService,
- getInspector,
- getNavigation,
- getData,
- getCoreI18n,
- getCoreChrome,
- getMapsCapabilities,
- getToasts,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/kibana_services';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { createMapStore } from '../../../../../plugins/maps/public/reducers/store';
-import { Provider } from 'react-redux';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { GisMap } from '../../../../../plugins/maps/public/connected_components/gis_map';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { addHelpMenuToAppChrome } from '../../../../../plugins/maps/public/help_menu_util';
-import {
- setSelectedLayer,
- setRefreshConfig,
- setGotoWithCenter,
- replaceLayerList,
- setQuery,
- setMapSettings,
- enableFullScreen,
- updateFlyout,
- setReadOnly,
- setIsLayerTOCOpen,
- setOpenTOCDetails,
- openMapSettings,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/actions';
-import {
- DEFAULT_IS_LAYER_TOC_OPEN,
- FLYOUT_STATE,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/reducers/ui';
-import {
- getIsFullScreen,
- getFlyoutDisplay,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/selectors/ui_selectors';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util';
-import {
- getQueryableUniqueIndexPatternIds,
- hasDirtyState,
- getLayerListRaw,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/selectors/map_selectors';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInitialLayers } from '../../../../../plugins/maps/public/angular/get_initial_layers';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInitialQuery } from '../../../../../plugins/maps/public/angular/get_initial_query';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInitialTimeFilters } from '../../../../../plugins/maps/public/angular/get_initial_time_filters';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInitialRefreshConfig } from '../../../../../plugins/maps/public/angular/get_initial_refresh_config';
-import { MAP_SAVED_OBJECT_TYPE, MAP_APP_PATH } from '../../../../../plugins/maps/common/constants';
-import { npSetup, npStart } from 'ui/new_platform';
-import { esFilters } from '../../../../../../src/plugins/data/public';
-import {
- SavedObjectSaveModal,
- showSaveModal,
-} from '../../../../../../src/plugins/saved_objects/public';
-import { loadKbnTopNavDirectives } from '../../../../../../src/plugins/kibana_legacy/public';
-import {
- bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins,
- bindStartCoreAndPlugins as bindNpStartCoreAndPlugins,
-} from '../../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-
-const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root';
-
-const app = uiModules.get(MAP_APP_PATH, []);
-
-// Init required services. Necessary while in legacy
-const config = _.get(npSetup, 'plugins.maps.config', {});
-const kibanaVersion = npSetup.core.injectedMetadata.getKibanaVersion();
-bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins, config, kibanaVersion);
-bindNpStartCoreAndPlugins(npStart.core, npStart.plugins);
-
-loadKbnTopNavDirectives(getNavigation().ui);
-
-function getInitialLayersFromUrlParam() {
- const locationSplit = window.location.href.split('?');
- if (locationSplit.length <= 1) {
- return [];
- }
- const mapAppParams = new URLSearchParams(locationSplit[1]);
- if (!mapAppParams.has('initialLayers')) {
- return [];
- }
-
- try {
- return rison.decode_array(mapAppParams.get('initialLayers'));
- } catch (e) {
- getToasts().addWarning({
- title: i18n.translate('xpack.maps.initialLayers.unableToParseTitle', {
- defaultMessage: `Inital layers not added to map`,
- }),
- text: i18n.translate('xpack.maps.initialLayers.unableToParseMessage', {
- defaultMessage: `Unable to parse contents of 'initialLayers' parameter. Error: {errorMsg}`,
- values: { errorMsg: e.message },
- }),
- });
- return [];
- }
-}
-
-app.controller(
- 'GisMapController',
- ($scope, $route, kbnUrl, localStorage, AppState, globalState) => {
- const savedQueryService = getData().query.savedQueries;
- const { filterManager } = getData().query;
- const savedMap = $route.current.locals.map;
- $scope.screenTitle = savedMap.title;
- let unsubscribe;
- let initialLayerListConfig;
- const $state = new AppState();
- const store = createMapStore();
-
- function getAppStateFilters() {
- return _.get($state, 'filters', []);
- }
-
- const visibleSubscription = getCoreChrome()
- .getIsVisible$()
- .subscribe((isVisible) => {
- $scope.$evalAsync(() => {
- $scope.isVisible = isVisible;
- });
- });
-
- $scope.$listen(globalState, 'fetch_with_changes', (diff) => {
- if (diff.includes('time') || diff.includes('filters')) {
- onQueryChange({
- filters: [...globalState.filters, ...getAppStateFilters()],
- time: globalState.time,
- });
- }
- if (diff.includes('refreshInterval')) {
- $scope.onRefreshChange({ isPaused: globalState.pause, refreshInterval: globalState.value });
- }
- });
-
- $scope.$listen($state, 'fetch_with_changes', function (diff) {
- if ((diff.includes('query') || diff.includes('filters')) && $state.query) {
- onQueryChange({
- filters: [...globalState.filters, ...getAppStateFilters()],
- query: $state.query,
- });
- }
- });
-
- function syncAppAndGlobalState() {
- $scope.$evalAsync(() => {
- // appState
- $state.query = $scope.query;
- $state.filters = filterManager.getAppFilters();
- $state.save();
-
- // globalState
- globalState.time = $scope.time;
- globalState.refreshInterval = {
- pause: $scope.refreshConfig.isPaused,
- value: $scope.refreshConfig.interval,
- };
- globalState.filters = filterManager.getGlobalFilters();
- globalState.save();
- });
- }
-
- $scope.query = getInitialQuery({
- mapStateJSON: savedMap.mapStateJSON,
- appState: $state,
- userQueryLanguage: localStorage.get('kibana.userQueryLanguage'),
- });
- $scope.time = getInitialTimeFilters({
- mapStateJSON: savedMap.mapStateJSON,
- globalState: globalState,
- });
- $scope.refreshConfig = getInitialRefreshConfig({
- mapStateJSON: savedMap.mapStateJSON,
- globalState: globalState,
- });
-
- /* Saved Queries */
- $scope.showSaveQuery = getMapsCapabilities().saveQuery;
-
- $scope.$watch(
- () => getMapsCapabilities().saveQuery,
- (newCapability) => {
- $scope.showSaveQuery = newCapability;
- }
- );
-
- $scope.onQuerySaved = (savedQuery) => {
- $scope.savedQuery = savedQuery;
- };
-
- $scope.onSavedQueryUpdated = (savedQuery) => {
- $scope.savedQuery = { ...savedQuery };
- };
-
- $scope.onClearSavedQuery = () => {
- delete $scope.savedQuery;
- delete $state.savedQuery;
- onQueryChange({
- filters: filterManager.getGlobalFilters(),
- query: {
- query: '',
- language: localStorage.get('kibana.userQueryLanguage'),
- },
- });
- };
-
- function updateStateFromSavedQuery(savedQuery) {
- const savedQueryFilters = savedQuery.attributes.filters || [];
- const globalFilters = filterManager.getGlobalFilters();
- const allFilters = [...savedQueryFilters, ...globalFilters];
-
- if (savedQuery.attributes.timefilter) {
- if (savedQuery.attributes.timefilter.refreshInterval) {
- $scope.onRefreshChange({
- isPaused: savedQuery.attributes.timefilter.refreshInterval.pause,
- refreshInterval: savedQuery.attributes.timefilter.refreshInterval.value,
- });
- }
- onQueryChange({
- filters: allFilters,
- query: savedQuery.attributes.query,
- time: savedQuery.attributes.timefilter,
- });
- } else {
- onQueryChange({
- filters: allFilters,
- query: savedQuery.attributes.query,
- });
- }
- }
-
- $scope.$watch('savedQuery', (newSavedQuery) => {
- if (!newSavedQuery) return;
-
- $state.savedQuery = newSavedQuery.id;
- updateStateFromSavedQuery(newSavedQuery);
- });
-
- $scope.$watch(
- () => $state.savedQuery,
- (newSavedQueryId) => {
- if (!newSavedQueryId) {
- $scope.savedQuery = undefined;
- return;
- }
- if ($scope.savedQuery && newSavedQueryId !== $scope.savedQuery.id) {
- savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => {
- $scope.$evalAsync(() => {
- $scope.savedQuery = savedQuery;
- updateStateFromSavedQuery(savedQuery);
- });
- });
- }
- }
- );
- /* End of Saved Queries */
- async function onQueryChange({ filters, query, time, refresh }) {
- if (filters) {
- filterManager.setFilters(filters); // Maps and merges filters
- $scope.filters = filterManager.getFilters();
- }
- if (query) {
- $scope.query = query;
- }
- if (time) {
- $scope.time = time;
- }
- syncAppAndGlobalState();
- dispatchSetQuery(refresh);
- }
-
- function dispatchSetQuery(refresh) {
- store.dispatch(
- setQuery({
- filters: $scope.filters,
- query: $scope.query,
- timeFilters: $scope.time,
- refresh,
- })
- );
- }
-
- $scope.indexPatterns = [];
- $scope.onQuerySubmit = function ({ dateRange, query }) {
- onQueryChange({
- query,
- time: dateRange,
- refresh: true,
- });
- };
- $scope.updateFiltersAndDispatch = function (filters) {
- onQueryChange({
- filters,
- });
- };
- $scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
- $scope.refreshConfig = {
- isPaused,
- interval: refreshInterval ? refreshInterval : $scope.refreshConfig.interval,
- };
- syncAppAndGlobalState();
-
- store.dispatch(setRefreshConfig($scope.refreshConfig));
- };
-
- function addFilters(newFilters) {
- newFilters.forEach((filter) => {
- filter.$state = { store: esFilters.FilterStateStore.APP_STATE };
- });
- $scope.updateFiltersAndDispatch([...$scope.filters, ...newFilters]);
- }
-
- function hasUnsavedChanges() {
- const state = store.getState();
- const layerList = getLayerListRaw(state);
- const layerListConfigOnly = copyPersistentState(layerList);
-
- const savedLayerList = savedMap.getLayerList();
-
- return !savedLayerList
- ? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
- : // savedMap stores layerList as a JSON string using JSON.stringify.
- // JSON.stringify removes undefined properties from objects.
- // savedMap.getLayerList converts the JSON string back into Javascript array of objects.
- // Need to perform the same process for layerListConfigOnly to compare apples to apples
- // and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
- !_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
- }
-
- function isOnMapNow() {
- return window.location.hash.startsWith(`#/${MAP_SAVED_OBJECT_TYPE}`);
- }
-
- function beforeUnload(event) {
- if (!isOnMapNow()) {
- return;
- }
-
- const hasChanged = hasUnsavedChanges();
- if (hasChanged) {
- event.preventDefault();
- event.returnValue = 'foobar'; //this is required for Chrome
- }
- }
- window.addEventListener('beforeunload', beforeUnload);
-
- async function renderMap() {
- // clear old UI state
- store.dispatch(setSelectedLayer(null));
- store.dispatch(updateFlyout(FLYOUT_STATE.NONE));
- store.dispatch(setReadOnly(!getMapsCapabilities().save));
-
- handleStoreChanges(store);
- unsubscribe = store.subscribe(() => {
- handleStoreChanges(store);
- });
-
- // sync store with savedMap mapState
- let savedObjectFilters = [];
- if (savedMap.mapStateJSON) {
- const mapState = JSON.parse(savedMap.mapStateJSON);
- store.dispatch(
- setGotoWithCenter({
- lat: mapState.center.lat,
- lon: mapState.center.lon,
- zoom: mapState.zoom,
- })
- );
- if (mapState.filters) {
- savedObjectFilters = mapState.filters;
- }
- if (mapState.settings) {
- store.dispatch(setMapSettings(mapState.settings));
- }
- }
-
- if (savedMap.uiStateJSON) {
- const uiState = JSON.parse(savedMap.uiStateJSON);
- store.dispatch(
- setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN))
- );
- store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
- }
-
- const layerList = getInitialLayers(savedMap.layerListJSON, getInitialLayersFromUrlParam());
- initialLayerListConfig = copyPersistentState(layerList);
- store.dispatch(replaceLayerList(layerList));
- store.dispatch(setRefreshConfig($scope.refreshConfig));
-
- const initialFilters = [
- ..._.get(globalState, 'filters', []),
- ...getAppStateFilters(),
- ...savedObjectFilters,
- ];
- await onQueryChange({ filters: initialFilters });
-
- const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
- render(
-
-
-
-
- ,
- root
- );
- }
- renderMap();
-
- let prevIndexPatternIds;
- async function updateIndexPatterns(nextIndexPatternIds) {
- const indexPatterns = [];
- const getIndexPatternPromises = nextIndexPatternIds.map(async (indexPatternId) => {
- try {
- const indexPattern = await getIndexPatternService().get(indexPatternId);
- indexPatterns.push(indexPattern);
- } catch (err) {
- // unable to fetch index pattern
- }
- });
-
- await Promise.all(getIndexPatternPromises);
- // ignore outdated results
- if (prevIndexPatternIds !== nextIndexPatternIds) {
- return;
- }
- $scope.$evalAsync(() => {
- $scope.indexPatterns = indexPatterns;
- });
- }
-
- $scope.isFullScreen = false;
- $scope.isSaveDisabled = false;
- $scope.isOpenSettingsDisabled = false;
- function handleStoreChanges(store) {
- const nextIsFullScreen = getIsFullScreen(store.getState());
- if (nextIsFullScreen !== $scope.isFullScreen) {
- // Must trigger digest cycle for angular top nav to redraw itself when isFullScreen changes
- $scope.$evalAsync(() => {
- $scope.isFullScreen = nextIsFullScreen;
- });
- }
-
- const nextIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
- if (nextIndexPatternIds !== prevIndexPatternIds) {
- prevIndexPatternIds = nextIndexPatternIds;
- updateIndexPatterns(nextIndexPatternIds);
- }
-
- const nextIsSaveDisabled = hasDirtyState(store.getState());
- if (nextIsSaveDisabled !== $scope.isSaveDisabled) {
- $scope.$evalAsync(() => {
- $scope.isSaveDisabled = nextIsSaveDisabled;
- });
- }
-
- const flyoutDisplay = getFlyoutDisplay(store.getState());
- const nextIsOpenSettingsDisabled = flyoutDisplay !== FLYOUT_STATE.NONE;
- if (nextIsOpenSettingsDisabled !== $scope.isOpenSettingsDisabled) {
- $scope.$evalAsync(() => {
- $scope.isOpenSettingsDisabled = nextIsOpenSettingsDisabled;
- });
- }
- }
-
- $scope.$on('$destroy', () => {
- window.removeEventListener('beforeunload', beforeUnload);
- visibleSubscription.unsubscribe();
- getCoreChrome().setIsVisible(true);
-
- if (unsubscribe) {
- unsubscribe();
- }
- const node = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
- if (node) {
- unmountComponentAtNode(node);
- }
- });
-
- const updateBreadcrumbs = () => {
- getCoreChrome().setBreadcrumbs([
- {
- text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
- defaultMessage: 'Maps',
- }),
- onClick: () => {
- if (isOnMapNow() && hasUnsavedChanges()) {
- const navigateAway = window.confirm(
- i18n.translate('xpack.maps.mapController.unsavedChangesWarning', {
- defaultMessage: `Your unsaved changes might not be saved`,
- })
- );
- if (navigateAway) {
- window.location.hash = '#';
- }
- } else {
- window.location.hash = '#';
- }
- },
- },
- { text: savedMap.title },
- ]);
- };
- updateBreadcrumbs();
-
- addHelpMenuToAppChrome();
-
- async function doSave(saveOptions) {
- savedMap.syncWithStore(store.getState());
- let id;
-
- try {
- id = await savedMap.save(saveOptions);
- getCoreChrome().docTitle.change(savedMap.title);
- } catch (err) {
- getToasts().addDanger({
- title: i18n.translate('xpack.maps.mapController.saveErrorMessage', {
- defaultMessage: `Error on saving '{title}'`,
- values: { title: savedMap.title },
- }),
- text: err.message,
- 'data-test-subj': 'saveMapError',
- });
- return { error: err };
- }
-
- if (id) {
- getToasts().addSuccess({
- title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
- defaultMessage: `Saved '{title}'`,
- values: { title: savedMap.title },
- }),
- 'data-test-subj': 'saveMapSuccess',
- });
-
- updateBreadcrumbs();
-
- if (savedMap.id !== $route.current.params.id) {
- $scope.$evalAsync(() => {
- kbnUrl.change(`map/{{id}}`, { id: savedMap.id });
- });
- }
- }
- return { id };
- }
-
- // Hide angular timepicer/refresh UI from top nav
- getTimeFilter().disableTimeRangeSelector();
- getTimeFilter().disableAutoRefreshSelector();
- $scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar
- $scope.topNavMenu = [
- {
- id: 'full-screen',
- label: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', {
- defaultMessage: `full screen`,
- }),
- description: i18n.translate('xpack.maps.mapController.fullScreenDescription', {
- defaultMessage: `full screen`,
- }),
- testId: 'mapsFullScreenMode',
- run() {
- getCoreChrome().setIsVisible(false);
- store.dispatch(enableFullScreen());
- },
- },
- {
- id: 'inspect',
- label: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', {
- defaultMessage: `inspect`,
- }),
- description: i18n.translate('xpack.maps.mapController.openInspectorDescription', {
- defaultMessage: `Open Inspector`,
- }),
- testId: 'openInspectorButton',
- run() {
- const inspectorAdapters = getInspectorAdapters(store.getState());
- getInspector().open(inspectorAdapters, {});
- },
- },
- {
- id: 'mapSettings',
- label: i18n.translate('xpack.maps.mapController.openSettingsButtonLabel', {
- defaultMessage: `Map settings`,
- }),
- description: i18n.translate('xpack.maps.mapController.openSettingsDescription', {
- defaultMessage: `Open map settings`,
- }),
- testId: 'openSettingsButton',
- disableButton() {
- return $scope.isOpenSettingsDisabled;
- },
- run() {
- store.dispatch(openMapSettings());
- },
- },
- ...(getMapsCapabilities().save
- ? [
- {
- id: 'save',
- label: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', {
- defaultMessage: `save`,
- }),
- description: i18n.translate('xpack.maps.mapController.saveMapDescription', {
- defaultMessage: `Save map`,
- }),
- testId: 'mapSaveButton',
- disableButton() {
- return $scope.isSaveDisabled;
- },
- tooltip() {
- if ($scope.isSaveDisabled) {
- return i18n.translate('xpack.maps.mapController.saveMapDisabledButtonTooltip', {
- defaultMessage: 'Save or Cancel your layer changes before saving',
- });
- }
- },
- run: async () => {
- const onSave = ({
- newTitle,
- newCopyOnSave,
- isTitleDuplicateConfirmed,
- onTitleDuplicate,
- }) => {
- const currentTitle = savedMap.title;
- savedMap.title = newTitle;
- savedMap.copyOnSave = newCopyOnSave;
- const saveOptions = {
- confirmOverwrite: false,
- isTitleDuplicateConfirmed,
- onTitleDuplicate,
- };
- return doSave(saveOptions).then((response) => {
- // If the save wasn't successful, put the original values back.
- if (!response.id || response.error) {
- savedMap.title = currentTitle;
- }
- return response;
- });
- };
-
- const saveModal = (
- {}}
- title={savedMap.title}
- showCopyOnSave={savedMap.id ? true : false}
- objectType={MAP_SAVED_OBJECT_TYPE}
- showDescription={false}
- />
- );
- showSaveModal(saveModal, getCoreI18n().Context);
- },
- },
- ]
- : []),
- ];
- }
-);
diff --git a/x-pack/legacy/plugins/maps/public/index.scss b/x-pack/legacy/plugins/maps/public/index.scss
deleted file mode 100644
index b2a228f01b92..000000000000
--- a/x-pack/legacy/plugins/maps/public/index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-/* GIS plugin styles */
-
-@import '../../../../plugins/maps/public/index';
diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts
deleted file mode 100644
index 89ce976de927..000000000000
--- a/x-pack/legacy/plugins/maps/public/index.ts
+++ /dev/null
@@ -1,34 +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.
- */
-
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import '../../../../plugins/maps/public/kibana_services';
-
-// import the uiExports that we want to "use"
-import 'uiExports/inspectorViews';
-import 'uiExports/search';
-import 'uiExports/embeddableFactories';
-import 'uiExports/embeddableActions';
-
-import 'ui/autoload/all';
-import 'react-vis/dist/style.css';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import '../../../../plugins/maps/public/angular/services/gis_map_saved_object_loader';
-import './angular/map_controller';
-import './routes';
-// @ts-ignore
-import { MapsPlugin } from './plugin';
-
-export const plugin = () => {
- return new MapsPlugin();
-};
-
-export {
- RenderTooltipContentParams,
- ITooltipProperty,
-} from '../../../../plugins/maps/public/classes/tooltips/tooltip_property'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-export { MapEmbeddable, MapEmbeddableInput } from '../../../../plugins/maps/public/embeddable';
diff --git a/x-pack/legacy/plugins/maps/public/legacy.ts b/x-pack/legacy/plugins/maps/public/legacy.ts
deleted file mode 100644
index bcbfca17755f..000000000000
--- a/x-pack/legacy/plugins/maps/public/legacy.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { npSetup, npStart } from 'ui/new_platform';
-// @ts-ignore Untyped Module
-import { uiModules } from 'ui/modules';
-import { plugin } from '.';
-
-const pluginInstance = plugin();
-
-const setupPlugins = {
- __LEGACY: {
- uiModules,
- },
- np: npSetup.plugins,
-};
-
-export const setup = pluginInstance.setup(npSetup.core, setupPlugins);
-export const start = pluginInstance.start(npStart.core, npStart.plugins);
diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts
deleted file mode 100644
index 9605c0d3e5fd..000000000000
--- a/x-pack/legacy/plugins/maps/public/plugin.ts
+++ /dev/null
@@ -1,70 +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 _ from 'lodash';
-import { Plugin, CoreStart, CoreSetup } from 'src/core/public';
-// @ts-ignore
-import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
-// @ts-ignore
-import { wrapInI18nContext } from 'ui/i18n';
-// @ts-ignore
-import { MapListing } from '../../../../plugins/maps/public/components/map_listing'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-// @ts-ignore
-import {
- bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins,
- bindStartCoreAndPlugins as bindNpStartCoreAndPlugins,
-} from '../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public';
-import { LicensingPluginSetup } from '../../../../plugins/licensing/public';
-import {
- DataPublicPluginSetup,
- DataPublicPluginStart,
-} from '../../../../../src/plugins/data/public';
-
-/**
- * These are the interfaces with your public contracts. You should export these
- * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.
- * @public
- */
-export type MapsPluginSetup = ReturnType;
-export type MapsPluginStart = ReturnType;
-
-interface MapsPluginSetupDependencies {
- __LEGACY: any;
- np: {
- licensing?: LicensingPluginSetup;
- home: HomePublicPluginSetup;
- data: DataPublicPluginSetup;
- };
-}
-
-interface MapsPluginStartDependencies {
- data: DataPublicPluginStart;
- inspector: InspectorStartContract;
- // file_upload TODO: Export type from file upload and use here
-}
-
-/** @internal */
-export class MapsPlugin implements Plugin {
- public setup(core: CoreSetup, { __LEGACY: { uiModules }, np }: MapsPluginSetupDependencies) {
- uiModules
- .get('app/maps', ['ngRoute', 'react'])
- .directive('mapListing', function (reactDirective: any) {
- return reactDirective(wrapInI18nContext(MapListing));
- });
-
- // @ts-ignore
- const config = _.get(np, 'maps.config', {});
- // @ts-ignore
- const kibanaVersion = core.injectedMetadata.getKibanaVersion();
- // @ts-ignore
- bindNpSetupCoreAndPlugins(core, np, config, kibanaVersion);
- }
-
- public start(core: CoreStart, plugins: MapsPluginStartDependencies) {
- bindNpStartCoreAndPlugins(core, plugins);
- }
-}
diff --git a/x-pack/legacy/plugins/maps/public/routes.js b/x-pack/legacy/plugins/maps/public/routes.js
deleted file mode 100644
index 20664b1b35a2..000000000000
--- a/x-pack/legacy/plugins/maps/public/routes.js
+++ /dev/null
@@ -1,108 +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 { i18n } from '@kbn/i18n';
-import routes from 'ui/routes';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import listingTemplate from '../../../../plugins/maps/public/angular/listing_ng_wrapper.html';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import mapTemplate from '../../../../plugins/maps/public/angular/map.html';
-import {
- getSavedObjectsClient,
- getCoreChrome,
- getMapsCapabilities,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../plugins/maps/public/kibana_services';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getMapsSavedObjectLoader } from '../../../../plugins/maps/public/angular/services/gis_map_saved_object_loader';
-import { LISTING_LIMIT_SETTING } from '../../../../../src/plugins/saved_objects/common';
-
-routes.enable();
-
-routes
- .defaults(/.*/, {
- badge: () => {
- if (getMapsCapabilities().save) {
- return undefined;
- }
-
- return {
- text: i18n.translate('xpack.maps.badge.readOnly.text', {
- defaultMessage: 'Read only',
- }),
- tooltip: i18n.translate('xpack.maps.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save maps',
- }),
- iconType: 'glasses',
- };
- },
- })
- .when('/', {
- template: listingTemplate,
- controller($scope, config) {
- const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
- $scope.listingLimit = config.get(LISTING_LIMIT_SETTING);
- $scope.find = (search) => {
- return gisMapSavedObjectLoader.find(search, $scope.listingLimit);
- };
- $scope.delete = (ids) => {
- return gisMapSavedObjectLoader.delete(ids);
- };
- $scope.readOnly = !getMapsCapabilities().save;
- },
- resolve: {
- hasMaps: function (kbnUrl) {
- getSavedObjectsClient()
- .find({ type: 'map', perPage: 1 })
- .then((resp) => {
- // Do not show empty listing page, just redirect to a new map
- if (resp.savedObjects.length === 0) {
- kbnUrl.redirect('/map');
- }
- return true;
- });
- },
- },
- })
- .when('/map', {
- template: mapTemplate,
- controller: 'GisMapController',
- resolve: {
- map: function (redirectWhenMissing) {
- const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
- return gisMapSavedObjectLoader.get().catch(
- redirectWhenMissing({
- map: '/',
- })
- );
- },
- },
- })
- .when('/map/:id', {
- template: mapTemplate,
- controller: 'GisMapController',
- resolve: {
- map: function (redirectWhenMissing, $route) {
- const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
- const id = $route.current.params.id;
- return gisMapSavedObjectLoader
- .get(id)
- .then((savedMap) => {
- getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, id);
- getCoreChrome().docTitle.change(savedMap.title);
- return savedMap;
- })
- .catch(
- redirectWhenMissing({
- map: '/',
- })
- );
- },
- },
- })
- .otherwise({
- redirectTo: '/',
- });
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
index faa2937aeaa1..2a3741e15f46 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
@@ -13,7 +13,8 @@ import {
EmbeddableExpression,
} from '../../expression_types';
import { getFunctionHelp } from '../../../i18n';
-import { MapEmbeddableInput } from '../../../../../legacy/plugins/maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MapEmbeddableInput } from '../../../../../plugins/maps/public/embeddable';
interface Arguments {
id: string;
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts
index e0e0aeaeea27..d910c734a697 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts
@@ -5,7 +5,7 @@
*/
import { toExpression } from './map';
-import { MapEmbeddableInput } from '../../../../../../legacy/plugins/maps/public';
+import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embeddable';
import { fromExpression, Ast } from '@kbn/interpreter/common';
const baseSavedMapInput = {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts
index 1f9bec133488..111fdc71fa24 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { MapEmbeddableInput } from '../../../../../../legacy/plugins/maps/public';
+import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embeddable';
export function toExpression(input: MapEmbeddableInput): string {
const expressionParts = [] as string[];
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index edb395633827..be3de22fa011 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -32,7 +32,7 @@ export const GIS_API_PATH = `api/${APP_ID}`;
export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`;
export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`;
-export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`;
+export const MAP_BASE_URL = `/${MAP_APP_PATH}/${MAP_SAVED_OBJECT_TYPE}`;
export function createMapPath(id: string) {
return `${MAP_BASE_URL}/${id}`;
diff --git a/x-pack/plugins/maps/public/angular/listing_ng_wrapper.html b/x-pack/plugins/maps/public/angular/listing_ng_wrapper.html
deleted file mode 100644
index bfea81e13e9d..000000000000
--- a/x-pack/plugins/maps/public/angular/listing_ng_wrapper.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/x-pack/plugins/maps/public/angular/map.html b/x-pack/plugins/maps/public/angular/map.html
deleted file mode 100644
index 7d7dcf6f9c9a..000000000000
--- a/x-pack/plugins/maps/public/angular/map.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
{{screenTitle}}
-
-
-
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js
index 811291de26d3..d5d24da22523 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js
@@ -29,14 +29,13 @@ async function fetchIndexSettings(indexPatternTitle) {
const http = getHttp();
const toasts = getToasts();
try {
- const indexSettings = await http.fetch(`../${INDEX_SETTINGS_API_PATH}`, {
+ return await http.fetch(`/${INDEX_SETTINGS_API_PATH}`, {
method: 'GET',
credentials: 'same-origin',
query: {
indexPatternTitle,
},
});
- return indexSettings;
} catch (err) {
const warningMsg = i18n.translate('xpack.maps.indexSettings.fetchErrorMsg', {
defaultMessage: `Unable to fetch index settings for index pattern '{indexPatternTitle}'.
diff --git a/x-pack/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/plugins/maps/public/connected_components/gis_map/view.js
index 122f5cdd45c1..7199620d69fc 100644
--- a/x-pack/plugins/maps/public/connected_components/gis_map/view.js
+++ b/x-pack/plugins/maps/public/connected_components/gis_map/view.js
@@ -14,7 +14,6 @@ import { LayerPanel } from '../layer_panel';
import { AddLayerPanel } from '../add_layer_panel';
import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui';
import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public';
-
import { getIndexPatternsFromIds } from '../../index_pattern_util';
import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
import { indexPatterns as indexPatternsUtils } from '../../../../../../src/plugins/data/public';
@@ -23,6 +22,7 @@ import uuid from 'uuid/v4';
import { FLYOUT_STATE } from '../../reducers/ui';
import { MapSettingsPanel } from '../map_settings_panel';
import { registerLayerWizards } from '../../classes/layers/load_layer_wizards';
+import 'mapbox-gl/dist/mapbox-gl.css';
const RENDER_COMPLETE_EVENT = 'renderComplete';
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
index b27f66ea085d..6f5833979221 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -8,7 +8,6 @@ import _ from 'lodash';
import React, { Suspense, lazy } from 'react';
import { Provider } from 'react-redux';
import { render, unmountComponentAtNode } from 'react-dom';
-import 'mapbox-gl/dist/mapbox-gl.css';
import { Subscription } from 'rxjs';
import { Unsubscribe } from 'redux';
import { EuiLoadingSpinner } from '@elastic/eui';
diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts
index 6a144e84b05e..7b5521443d97 100644
--- a/x-pack/plugins/maps/public/index.ts
+++ b/x-pack/plugins/maps/public/index.ts
@@ -17,5 +17,3 @@ export const plugin: PluginInitializer = (
};
export { MAP_SAVED_OBJECT_TYPE } from '../common/constants';
-export { ITooltipProperty } from './classes/tooltips/tooltip_property';
-export { MapsPluginStart } from './plugin';
diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
index 152412376fb0..ca4098ebfa80 100644
--- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
+++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
@@ -7,6 +7,7 @@
import { AnyAction } from 'redux';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { IndexPatternsService } from 'src/plugins/data/public/index_patterns';
+import { ReactElement } from 'react';
import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public';
import { LayerDescriptor } from '../../common/descriptor_types';
import { MapStore, MapStoreState } from '../reducers/store';
@@ -36,6 +37,7 @@ interface LazyLoadedMapModules {
initialLayers?: LayerDescriptor[]
) => LayerDescriptor[];
mergeInputWithSavedMap: any;
+ renderApp: (context: unknown, params: unknown) => ReactElement;
createSecurityLayerDescriptors: (
indexPatternId: string,
indexPatternTitle: string
@@ -49,7 +51,7 @@ export async function lazyLoadMapModules(): Promise {
loadModulesPromise = new Promise(async (resolve) => {
const {
- // @ts-ignore
+ // @ts-expect-error
getMapsSavedObjectLoader,
getQueryableUniqueIndexPatternIds,
MapEmbeddable,
@@ -60,6 +62,8 @@ export async function lazyLoadMapModules(): Promise {
addLayerWithoutDataSync,
getInitialLayers,
mergeInputWithSavedMap,
+ // @ts-expect-error
+ renderApp,
createSecurityLayerDescriptors,
} = await import('./lazy');
@@ -74,6 +78,7 @@ export async function lazyLoadMapModules(): Promise {
addLayerWithoutDataSync,
getInitialLayers,
mergeInputWithSavedMap,
+ renderApp,
createSecurityLayerDescriptors,
});
});
diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts
index 0600b8d5073c..4f9f01f8a1b3 100644
--- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts
+++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts
@@ -7,13 +7,15 @@
// These are map-dependencies of the embeddable.
// By lazy-loading these, the Maps-app can register the embeddable when the plugin mounts, without actually pulling all the code.
-// @ts-ignore
-export * from '../../angular/services/gis_map_saved_object_loader';
+// @ts-expect-error
+export * from '../../routing/bootstrap/services/gis_map_saved_object_loader';
export * from '../../embeddable/map_embeddable';
export * from '../../kibana_services';
export * from '../../reducers/store';
export * from '../../actions';
export * from '../../selectors/map_selectors';
-export * from '../../angular/get_initial_layers';
+export * from '../../routing/bootstrap/get_initial_layers';
export * from '../../embeddable/merge_input_with_saved_map';
+// @ts-expect-error
+export * from '../../routing/maps_router';
export * from '../../classes/layers/solution_layers/security';
diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js
index 14f31d7660a5..cb7b3db17eab 100644
--- a/x-pack/plugins/maps/public/maps_vis_type_alias.js
+++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js
@@ -28,7 +28,7 @@ The Maps app offers more functionality and is easier to use.`,
return {
aliasApp: APP_ID,
- aliasPath: `#/${MAP_SAVED_OBJECT_TYPE}`,
+ aliasPath: `/${MAP_SAVED_OBJECT_TYPE}`,
name: APP_ID,
title: i18n.translate('xpack.maps.visTypeAlias.title', {
defaultMessage: 'Maps',
diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts
index e0639c9d0f36..412e8832453b 100644
--- a/x-pack/plugins/maps/public/plugin.ts
+++ b/x-pack/plugins/maps/public/plugin.ts
@@ -4,8 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public';
+import {
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ PluginInitializerContext,
+ DEFAULT_APP_CATEGORIES,
+} from '../../../../src/core/public';
// @ts-ignore
import { MapView } from './inspector/views/map_view';
import {
@@ -41,11 +47,13 @@ import { featureCatalogueEntry } from './feature_catalogue_entry';
import { getMapsVisTypeAlias } from './maps_vis_type_alias';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public';
-import { APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
+import { APP_ICON, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory';
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public';
-import { MapsConfigType, MapsXPackConfig } from '../config';
+import { MapsXPackConfig, MapsConfigType } from '../config';
+import { getAppTitle } from '../common/i18n_getters';
import { ILicense } from '../../licensing/common/types';
+import { lazyLoadMapModules } from './lazy_load_bundle';
import { MapsStartApi } from './api';
import { createSecurityLayerDescriptors } from './api/create_security_layer_descriptors';
@@ -140,9 +148,22 @@ export class MapsPlugin
home.featureCatalogue.register(featureCatalogueEntry);
visualizations.registerAlias(getMapsVisTypeAlias());
embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
- return {
- config,
- };
+
+ core.application.register({
+ id: APP_ID,
+ title: getAppTitle(),
+ order: 4000,
+ icon: `plugins/${APP_ID}/icon.svg`,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.kibana,
+ // @ts-expect-error
+ async mount(context, params) {
+ const [coreStart, startPlugins] = await core.getStartServices();
+ bindStartCoreAndPlugins(coreStart, startPlugins);
+ const { renderApp } = await lazyLoadMapModules();
+ return renderApp(context, params);
+ },
+ });
}
public start(core: CoreStart, plugins: any): MapsStartApi {
diff --git a/x-pack/plugins/maps/public/reducers/store.js b/x-pack/plugins/maps/public/reducers/store.js
index a1bc6b8e7013..c63f5f7fd82f 100644
--- a/x-pack/plugins/maps/public/reducers/store.js
+++ b/x-pack/plugins/maps/public/reducers/store.js
@@ -9,6 +9,7 @@ import thunk from 'redux-thunk';
import { ui, DEFAULT_MAP_UI_STATE } from './ui';
import { map, DEFAULT_MAP_STATE } from './map'; // eslint-disable-line import/named
import { nonSerializableInstances } from './non_serializable_instances';
+import { MAP_DESTROYED } from '../actions';
export const DEFAULT_MAP_STORE_STATE = {
ui: { ...DEFAULT_MAP_UI_STATE },
@@ -17,11 +18,21 @@ export const DEFAULT_MAP_STORE_STATE = {
export function createMapStore() {
const enhancers = [applyMiddleware(thunk)];
- const rootReducer = combineReducers({
+ const combinedReducers = combineReducers({
map,
ui,
nonSerializableInstances,
});
+
+ const rootReducer = (state, action) => {
+ // Reset store on map destroyed
+ if (action.type === MAP_DESTROYED) {
+ state = undefined;
+ }
+
+ return combinedReducers(state, action);
+ };
+
const storeConfig = {};
return createStore(rootReducer, storeConfig, compose(...enhancers));
}
diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.d.ts b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.d.ts
similarity index 84%
rename from x-pack/plugins/maps/public/angular/get_initial_layers.d.ts
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.d.ts
index 5b77ea6f514f..a23e715a0829 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_layers.d.ts
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LayerDescriptor } from '../../common/descriptor_types';
+import { LayerDescriptor } from '../../../common/descriptor_types';
export function getInitialLayers(
layerListJSON?: string,
diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
similarity index 57%
rename from x-pack/plugins/maps/public/angular/get_initial_layers.js
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
index 598fd6ce324d..cf6f51c8aeac 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_layers.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
@@ -5,25 +5,24 @@
*/
import _ from 'lodash';
// Import each layer type, even those not used, to init in registry
-import '../classes/sources/wms_source';
-import '../classes/sources/ems_file_source';
-import '../classes/sources/es_search_source';
-import '../classes/sources/es_pew_pew_source';
-import '../classes/sources/kibana_regionmap_source';
-import '../classes/sources/es_geo_grid_source';
-import '../classes/sources/xyz_tms_source';
-import { KibanaTilemapSource } from '../classes/sources/kibana_tilemap_source';
-import { TileLayer } from '../classes/layers/tile_layer/tile_layer';
-import { EMSTMSSource } from '../classes/sources/ems_tms_source';
-import { VectorTileLayer } from '../classes/layers/vector_tile_layer/vector_tile_layer';
-import { getIsEmsEnabled } from '../kibana_services';
-import { getKibanaTileMap } from '../meta';
+import '../../classes/sources/wms_source';
+import '../../classes/sources/ems_file_source';
+import '../../classes/sources/es_search_source';
+import '../../classes/sources/es_pew_pew_source';
+import '../../classes/sources/kibana_regionmap_source';
+import '../../classes/sources/es_geo_grid_source';
+import '../../classes/sources/xyz_tms_source';
+import { KibanaTilemapSource } from '../../classes/sources/kibana_tilemap_source';
+import { TileLayer } from '../../classes/layers/tile_layer/tile_layer';
+import { EMSTMSSource } from '../../classes/sources/ems_tms_source';
+import { VectorTileLayer } from '../../classes/layers/vector_tile_layer/vector_tile_layer';
+import { getIsEmsEnabled } from '../../kibana_services';
+import { getKibanaTileMap } from '../../meta';
export function getInitialLayers(layerListJSON, initialLayers = []) {
if (layerListJSON) {
return JSON.parse(layerListJSON);
}
-
const tilemapSourceFromKibana = getKibanaTileMap();
if (_.get(tilemapSourceFromKibana, 'url')) {
const layerDescriptor = TileLayer.createDescriptor({
diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js
similarity index 82%
rename from x-pack/plugins/maps/public/angular/get_initial_layers.test.js
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js
index 8ddcd7cb5bbb..4de29e6f028e 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-jest.mock('../meta', () => {
+jest.mock('../../meta', () => {
return {};
});
-jest.mock('../kibana_services');
+jest.mock('../../kibana_services');
import { getInitialLayers } from './get_initial_layers';
@@ -15,7 +15,7 @@ const layerListNotProvided = undefined;
describe('Saved object has layer list', () => {
beforeEach(() => {
- require('../kibana_services').getIsEmsEnabled = () => true;
+ require('../../kibana_services').getIsEmsEnabled = () => true;
});
it('Should get initial layers from saved object', () => {
@@ -32,7 +32,7 @@ describe('Saved object has layer list', () => {
describe('kibana.yml configured with map.tilemap.url', () => {
beforeAll(() => {
- require('../meta').getKibanaTileMap = () => {
+ require('../../meta').getKibanaTileMap = () => {
return {
url: 'myTileUrl',
};
@@ -62,11 +62,11 @@ describe('kibana.yml configured with map.tilemap.url', () => {
describe('EMS is enabled', () => {
beforeAll(() => {
- require('../meta').getKibanaTileMap = () => {
+ require('../../meta').getKibanaTileMap = () => {
return null;
};
- require('../kibana_services').getIsEmsEnabled = () => true;
- require('../kibana_services').getEmsTileLayerId = () => ({
+ require('../../kibana_services').getIsEmsEnabled = () => true;
+ require('../../kibana_services').getEmsTileLayerId = () => ({
bright: 'road_map',
desaturated: 'road_map_desaturated',
dark: 'dark_map',
@@ -98,10 +98,10 @@ describe('EMS is enabled', () => {
describe('EMS is not enabled', () => {
beforeAll(() => {
- require('../meta').getKibanaTileMap = () => {
+ require('../../meta').getKibanaTileMap = () => {
return null;
};
- require('../kibana_services').getIsEmsEnabled = () => false;
+ require('../../kibana_services').getIsEmsEnabled = () => false;
});
it('Should return empty layer list since there are no configured tile layers', () => {
diff --git a/x-pack/plugins/maps/public/angular/get_initial_query.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js
similarity index 84%
rename from x-pack/plugins/maps/public/angular/get_initial_query.js
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js
index 84f431cf2b3b..dfc3a1c9de96 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_query.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getUiSettings } from '../kibana_services';
-import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
+import { getUiSettings } from '../../kibana_services';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) {
const settings = getUiSettings();
diff --git a/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.js
similarity index 86%
rename from x-pack/plugins/maps/public/angular/get_initial_refresh_config.js
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.js
index 17a50c6c5f68..d7b3bbf5b4ab 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.js
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getUiSettings } from '../kibana_services';
-import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
+import { getUiSettings } from '../../kibana_services';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
export function getInitialRefreshConfig({ mapStateJSON, globalState = {} }) {
const uiSettings = getUiSettings();
diff --git a/x-pack/plugins/maps/public/angular/get_initial_time_filters.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.js
similarity index 90%
rename from x-pack/plugins/maps/public/angular/get_initial_time_filters.js
rename to x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.js
index 75d9f0e95ccf..9c11dabe0392 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_time_filters.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.js
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getUiSettings } from '../kibana_services';
+import { getUiSettings } from '../../kibana_services';
-export function getInitialTimeFilters({ mapStateJSON, globalState = {} }) {
+export function getInitialTimeFilters({ mapStateJSON, globalState }) {
if (mapStateJSON) {
const mapState = JSON.parse(mapStateJSON);
if (mapState.timeFilters) {
diff --git a/x-pack/plugins/maps/public/angular/services/gis_map_saved_object_loader.js b/x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.js
similarity index 87%
rename from x-pack/plugins/maps/public/angular/services/gis_map_saved_object_loader.js
rename to x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.js
index ea48eecf4e42..6c7e141c2ab5 100644
--- a/x-pack/plugins/maps/public/angular/services/gis_map_saved_object_loader.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.js
@@ -6,14 +6,14 @@
import _ from 'lodash';
import { createSavedGisMapClass } from './saved_gis_map';
-import { SavedObjectLoader } from '../../../../../../src/plugins/saved_objects/public';
+import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public';
import {
getCoreChrome,
getSavedObjectsClient,
getIndexPatternService,
getCoreOverlays,
getData,
-} from '../../kibana_services';
+} from '../../../kibana_services';
export const getMapsSavedObjectLoader = _.once(function () {
const services = {
diff --git a/x-pack/plugins/maps/public/angular/services/saved_gis_map.js b/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
similarity index 82%
rename from x-pack/plugins/maps/public/angular/services/saved_gis_map.js
rename to x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
index a0beaa768888..f24c7be65afa 100644
--- a/x-pack/plugins/maps/public/angular/services/saved_gis_map.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
@@ -5,7 +5,7 @@
*/
import _ from 'lodash';
-import { createSavedObjectClass } from '../../../../../../src/plugins/saved_objects/public';
+import { createSavedObjectClass } from '../../../../../../../src/plugins/saved_objects/public';
import {
getTimeFilters,
getMapZoom,
@@ -15,11 +15,12 @@ import {
getQuery,
getFilters,
getMapSettings,
-} from '../../selectors/map_selectors';
-import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors';
-import { copyPersistentState } from '../../reducers/util';
-import { extractReferences, injectReferences } from '../../../common/migrations/references';
-import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants';
+} from '../../../selectors/map_selectors';
+import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors';
+import { copyPersistentState } from '../../../reducers/util';
+import { extractReferences, injectReferences } from '../../../../common/migrations/references';
+import { MAP_BASE_URL, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
+import { getStore } from '../../store_operations';
export function createSavedGisMapClass(services) {
const SavedObjectClass = createSavedObjectClass(services);
@@ -73,14 +74,17 @@ export function createSavedGisMapClass(services) {
});
this.showInRecentlyAccessed = true;
}
+
getFullPath() {
- return `/app/maps#map/${this.id}`;
+ return `${MAP_BASE_URL}/${this.id}`;
}
+
getLayerList() {
return this.layerListJSON ? JSON.parse(this.layerListJSON) : null;
}
- syncWithStore(state) {
+ syncWithStore() {
+ const state = getStore().getState();
const layerList = getLayerListRaw(state);
const layerListConfigOnly = copyPersistentState(layerList);
this.layerListJSON = JSON.stringify(layerListConfigOnly);
diff --git a/x-pack/plugins/maps/public/routing/maps_router.js b/x-pack/plugins/maps/public/routing/maps_router.js
new file mode 100644
index 000000000000..840d4f2c6692
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/maps_router.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { Router, Switch, Route, Redirect } from 'react-router-dom';
+import { getCoreI18n } from '../kibana_services';
+import { createKbnUrlStateStorage } from '../../../../../src/plugins/kibana_utils/public';
+import { getStore } from './store_operations';
+import { Provider } from 'react-redux';
+import { LoadListAndRender } from './routes/list/load_list_and_render';
+import { LoadMapAndRender } from './routes/maps_app/load_map_and_render';
+
+export let goToSpecifiedPath;
+export let kbnUrlStateStorage;
+
+export async function renderApp(context, { appBasePath, element, history }) {
+ goToSpecifiedPath = (path) => history.push(path);
+ kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });
+
+ render( , element);
+
+ return () => {
+ unmountComponentAtNode(element);
+ };
+}
+
+const App = ({ history, appBasePath }) => {
+ const store = getStore();
+ const I18nContext = getCoreI18n().Context;
+
+ return (
+
+
+
+
+
+
+ // Redirect other routes to list, or if hash-containing, their non-hash equivalents
+ {
+ if (hash) {
+ // Remove leading hash
+ const newPath = hash.substr(1);
+ return ;
+ } else if (pathname === '/' || pathname === '') {
+ return ;
+ } else {
+ return ;
+ }
+ }}
+ />
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js b/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
new file mode 100644
index 000000000000..36a355719d94
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { getCoreChrome } from '../../kibana_services';
+import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants';
+import _ from 'lodash';
+import { getLayerListRaw } from '../../selectors/map_selectors';
+import { copyPersistentState } from '../../reducers/util';
+import { getStore } from '../store_operations';
+import { goToSpecifiedPath } from '../maps_router';
+
+function hasUnsavedChanges(savedMap, initialLayerListConfig) {
+ const state = getStore().getState();
+ const layerList = getLayerListRaw(state);
+ const layerListConfigOnly = copyPersistentState(layerList);
+
+ const savedLayerList = savedMap.getLayerList();
+
+ return !savedLayerList
+ ? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
+ : // savedMap stores layerList as a JSON string using JSON.stringify.
+ // JSON.stringify removes undefined properties from objects.
+ // savedMap.getLayerList converts the JSON string back into Javascript array of objects.
+ // Need to perform the same process for layerListConfigOnly to compare apples to apples
+ // and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
+ !_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
+}
+
+export const updateBreadcrumbs = (savedMap, initialLayerListConfig, currentPath = '') => {
+ const isOnMapNow = currentPath.startsWith(`/${MAP_SAVED_OBJECT_TYPE}`);
+ const breadCrumbs = isOnMapNow
+ ? [
+ {
+ text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
+ defaultMessage: 'Maps',
+ }),
+ onClick: () => {
+ if (hasUnsavedChanges(savedMap, initialLayerListConfig)) {
+ const navigateAway = window.confirm(
+ i18n.translate('xpack.maps.breadCrumbs.unsavedChangesWarning', {
+ defaultMessage: `Your unsaved changes might not be saved`,
+ })
+ );
+ if (navigateAway) {
+ goToSpecifiedPath('/');
+ }
+ } else {
+ goToSpecifiedPath('/');
+ }
+ },
+ },
+ { text: savedMap.title },
+ ]
+ : [];
+ getCoreChrome().setBreadcrumbs(breadCrumbs);
+};
diff --git a/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/index.js b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/index.js
new file mode 100644
index 000000000000..2575bbb9df92
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/index.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { connect } from 'react-redux';
+import { MapsTopNavMenu } from './top_nav_menu';
+import {
+ enableFullScreen,
+ openMapSettings,
+ removePreviewLayers,
+ setRefreshConfig,
+ setSelectedLayer,
+ updateFlyout,
+} from '../../../actions';
+import { FLYOUT_STATE } from '../../../reducers/ui';
+import { getInspectorAdapters } from '../../../reducers/non_serializable_instances';
+import { getFlyoutDisplay } from '../../../selectors/ui_selectors';
+import { hasDirtyState } from '../../../selectors/map_selectors';
+
+function mapStateToProps(state = {}) {
+ return {
+ isOpenSettingsDisabled: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE,
+ inspectorAdapters: getInspectorAdapters(state),
+ isSaveDisabled: hasDirtyState(state),
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ closeFlyout: () => {
+ dispatch(setSelectedLayer(null));
+ dispatch(updateFlyout(FLYOUT_STATE.NONE));
+ dispatch(removePreviewLayers());
+ },
+ setRefreshStoreConfig: (refreshConfig) => dispatch(setRefreshConfig(refreshConfig)),
+ enableFullScreen: () => dispatch(enableFullScreen()),
+ openMapSettings: () => dispatch(openMapSettings()),
+ };
+}
+
+const connectedMapsTopNavMenu = connect(mapStateToProps, mapDispatchToProps)(MapsTopNavMenu);
+export { connectedMapsTopNavMenu as MapsTopNavMenu };
diff --git a/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js
new file mode 100644
index 000000000000..762d61d6d33f
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js
@@ -0,0 +1,279 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ getNavigation,
+ getCoreChrome,
+ getMapsCapabilities,
+ getInspector,
+ getToasts,
+ getCoreI18n,
+ getData,
+ getUiSettings,
+} from '../../../kibana_services';
+import {
+ SavedObjectSaveModal,
+ showSaveModal,
+} from '../../../../../../../src/plugins/saved_objects/public';
+import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
+import { updateBreadcrumbs } from '../breadcrumbs';
+import { goToSpecifiedPath } from '../../maps_router';
+
+export function MapsTopNavMenu({
+ savedMap,
+ query,
+ onQueryChange,
+ onQuerySaved,
+ onSavedQueryUpdated,
+ savedQuery,
+ time,
+ refreshConfig,
+ setRefreshConfig,
+ setRefreshStoreConfig,
+ initialLayerListConfig,
+ indexPatterns,
+ updateFiltersAndDispatch,
+ isSaveDisabled,
+ closeFlyout,
+ enableFullScreen,
+ openMapSettings,
+ inspectorAdapters,
+ syncAppAndGlobalState,
+ currentPath,
+ isOpenSettingsDisabled,
+}) {
+ const { TopNavMenu } = getNavigation().ui;
+ const { filterManager } = getData().query;
+ const showSaveQuery = getMapsCapabilities().saveQuery;
+ const onClearSavedQuery = () => {
+ onQuerySaved(undefined);
+ onQueryChange({
+ filters: filterManager.getGlobalFilters(),
+ query: {
+ query: '',
+ language: getUiSettings().get('search:queryLanguage'),
+ },
+ });
+ };
+
+ // Nav settings
+ const config = getTopNavConfig(
+ savedMap,
+ initialLayerListConfig,
+ isOpenSettingsDisabled,
+ isSaveDisabled,
+ closeFlyout,
+ enableFullScreen,
+ openMapSettings,
+ inspectorAdapters,
+ currentPath
+ );
+
+ const submitQuery = function ({ dateRange, query }) {
+ onQueryChange({
+ query,
+ time: dateRange,
+ refresh: true,
+ });
+ };
+
+ const onRefreshChange = function ({ isPaused, refreshInterval }) {
+ const newRefreshConfig = {
+ isPaused,
+ interval: isNaN(refreshInterval) ? refreshConfig.interval : refreshInterval,
+ };
+ setRefreshConfig(newRefreshConfig, () => {
+ setRefreshStoreConfig(newRefreshConfig);
+ syncAppAndGlobalState();
+ });
+ };
+
+ return (
+
+ );
+}
+
+function getTopNavConfig(
+ savedMap,
+ initialLayerListConfig,
+ isOpenSettingsDisabled,
+ isSaveDisabled,
+ closeFlyout,
+ enableFullScreen,
+ openMapSettings,
+ inspectorAdapters,
+ currentPath
+) {
+ return [
+ {
+ id: 'full-screen',
+ label: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', {
+ defaultMessage: `full screen`,
+ }),
+ description: i18n.translate('xpack.maps.mapController.fullScreenDescription', {
+ defaultMessage: `full screen`,
+ }),
+ testId: 'mapsFullScreenMode',
+ run() {
+ getCoreChrome().setIsVisible(false);
+ enableFullScreen();
+ },
+ },
+ {
+ id: 'inspect',
+ label: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', {
+ defaultMessage: `inspect`,
+ }),
+ description: i18n.translate('xpack.maps.mapController.openInspectorDescription', {
+ defaultMessage: `Open Inspector`,
+ }),
+ testId: 'openInspectorButton',
+ run() {
+ getInspector().open(inspectorAdapters, {});
+ },
+ },
+ {
+ id: 'mapSettings',
+ label: i18n.translate('xpack.maps.mapController.openSettingsButtonLabel', {
+ defaultMessage: `Map settings`,
+ }),
+ description: i18n.translate('xpack.maps.mapController.openSettingsDescription', {
+ defaultMessage: `Open map settings`,
+ }),
+ testId: 'openSettingsButton',
+ disableButton() {
+ return isOpenSettingsDisabled;
+ },
+ run() {
+ openMapSettings();
+ },
+ },
+ ...(getMapsCapabilities().save
+ ? [
+ {
+ id: 'save',
+ label: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', {
+ defaultMessage: `save`,
+ }),
+ description: i18n.translate('xpack.maps.mapController.saveMapDescription', {
+ defaultMessage: `Save map`,
+ }),
+ testId: 'mapSaveButton',
+ disableButton() {
+ return isSaveDisabled;
+ },
+ tooltip() {
+ if (isSaveDisabled) {
+ return i18n.translate('xpack.maps.mapController.saveMapDisabledButtonTooltip', {
+ defaultMessage: 'Save or Cancel your layer changes before saving',
+ });
+ }
+ },
+ run: async () => {
+ const onSave = ({
+ newTitle,
+ newCopyOnSave,
+ isTitleDuplicateConfirmed,
+ onTitleDuplicate,
+ }) => {
+ const currentTitle = savedMap.title;
+ savedMap.title = newTitle;
+ savedMap.copyOnSave = newCopyOnSave;
+ const saveOptions = {
+ confirmOverwrite: false,
+ isTitleDuplicateConfirmed,
+ onTitleDuplicate,
+ };
+ return doSave(
+ savedMap,
+ saveOptions,
+ initialLayerListConfig,
+ closeFlyout,
+ currentPath
+ ).then((response) => {
+ // If the save wasn't successful, put the original values back.
+ if (!response.id || response.error) {
+ savedMap.title = currentTitle;
+ }
+ return response;
+ });
+ };
+
+ const saveModal = (
+ {}}
+ title={savedMap.title}
+ showCopyOnSave={!!savedMap.id}
+ objectType={MAP_SAVED_OBJECT_TYPE}
+ showDescription={false}
+ />
+ );
+ showSaveModal(saveModal, getCoreI18n().Context);
+ },
+ },
+ ]
+ : []),
+ ];
+}
+
+async function doSave(savedMap, saveOptions, initialLayerListConfig, closeFlyout, currentPath) {
+ closeFlyout();
+ savedMap.syncWithStore();
+ let id;
+
+ try {
+ id = await savedMap.save(saveOptions);
+ getCoreChrome().docTitle.change(savedMap.title);
+ } catch (err) {
+ getToasts().addDanger({
+ title: i18n.translate('xpack.maps.mapController.saveErrorMessage', {
+ defaultMessage: `Error on saving '{title}'`,
+ values: { title: savedMap.title },
+ }),
+ text: err.message,
+ 'data-test-subj': 'saveMapError',
+ });
+ return { error: err };
+ }
+
+ if (id) {
+ goToSpecifiedPath(`/map/${id}${window.location.hash}`);
+ updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
+
+ getToasts().addSuccess({
+ title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
+ defaultMessage: `Saved '{title}'`,
+ values: { title: savedMap.title },
+ }),
+ 'data-test-subj': 'saveMapSuccess',
+ });
+ }
+ return { id };
+}
diff --git a/x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.js b/x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.js
new file mode 100644
index 000000000000..ee1307956071
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
+import { getToasts } from '../../../kibana_services';
+import { i18n } from '@kbn/i18n';
+import { MapsListView } from './maps_list_view';
+import { Redirect } from 'react-router-dom';
+
+export class LoadListAndRender extends React.Component {
+ state = {
+ mapsLoaded: false,
+ hasSavedMaps: null,
+ };
+
+ componentDidMount() {
+ this._isMounted = true;
+ this._loadMapsList();
+ }
+
+ componentWillUnmount() {
+ this._isMounted = false;
+ }
+
+ async _loadMapsList() {
+ try {
+ const { hits = [] } = await getMapsSavedObjectLoader().find('', 1);
+ if (this._isMounted) {
+ this.setState({ mapsLoaded: true, hasSavedMaps: !!hits.length });
+ }
+ } catch (err) {
+ if (this._isMounted) {
+ this.setState({ mapsLoaded: true, hasSavedMaps: false });
+ getToasts().addDanger({
+ title: i18n.translate('xpack.maps.mapListing.errorAttemptingToLoadSavedMaps', {
+ defaultMessage: `Unable to load maps`,
+ }),
+ text: `${err}`,
+ });
+ }
+ }
+ }
+
+ render() {
+ const { mapsLoaded, hasSavedMaps } = this.state;
+
+ if (mapsLoaded) {
+ return hasSavedMaps ? : ;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/x-pack/plugins/maps/public/components/map_listing.js b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js
similarity index 86%
rename from x-pack/plugins/maps/public/components/map_listing.js
rename to x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js
index 030b67185c10..a32bd00dbae5 100644
--- a/x-pack/plugins/maps/public/components/map_listing.js
+++ b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js
@@ -5,10 +5,14 @@
*/
import React from 'react';
-import PropTypes from 'prop-types';
import _ from 'lodash';
-
-import { getToasts } from '../kibana_services';
+import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
+import {
+ getMapsCapabilities,
+ getUiSettings,
+ getToasts,
+ getCoreChrome,
+} from '../../../kibana_services';
import {
EuiTitle,
EuiFieldSearch,
@@ -27,11 +31,14 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { addHelpMenuToAppChrome } from '../help_menu_util';
+import { addHelpMenuToAppChrome } from '../../../help_menu_util';
+import { Link } from 'react-router-dom';
+import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
+import { goToSpecifiedPath } from '../../maps_router';
export const EMPTY_FILTER = '';
-export class MapListing extends React.Component {
+export class MapsListView extends React.Component {
state = {
hasInitialFetchReturned: false,
isFetchingItems: false,
@@ -42,10 +49,13 @@ export class MapListing extends React.Component {
selectedIds: [],
page: 0,
perPage: 20,
+ readOnly: !getMapsCapabilities().save,
+ listingLimit: getUiSettings().get('savedObjects:listingLimit'),
};
UNSAFE_componentWillMount() {
this._isMounted = true;
+ updateBreadcrumbs();
}
componentWillUnmount() {
@@ -54,12 +64,21 @@ export class MapListing extends React.Component {
}
componentDidMount() {
+ this.initMapList();
+ }
+
+ async initMapList() {
this.fetchItems();
addHelpMenuToAppChrome();
+ getCoreChrome().docTitle.change('Maps');
}
+ _find = (search) => getMapsSavedObjectLoader().find(search, this.state.listingLimit);
+
+ _delete = (ids) => getMapsSavedObjectLoader().delete(ids);
+
debouncedFetch = _.debounce(async (filter) => {
- const response = await this.props.find(filter);
+ const response = await this._find(filter);
if (!this._isMounted) {
return;
@@ -73,7 +92,7 @@ export class MapListing extends React.Component {
isFetchingItems: false,
items: response.hits,
totalItems: response.total,
- showLimitError: response.total > this.props.listingLimit,
+ showLimitError: response.total > this.state.listingLimit,
});
}
}, 300);
@@ -89,7 +108,7 @@ export class MapListing extends React.Component {
deleteSelectedItems = async () => {
try {
- await this.props.delete(this.state.selectedIds);
+ await this._delete(this.state.selectedIds);
} catch (error) {
getToasts().addDanger({
title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', {
@@ -211,11 +230,11 @@ export class MapListing extends React.Component {
listingLimit setting prevents the table below from displaying more than {listingLimit}.
+ You can change this setting under "
values={{
totalItems: this.state.totalItems,
- listingLimit: this.props.listingLimit,
+ listingLimit: this.state.listingLimit,
}}
/>
@@ -307,7 +326,10 @@ export class MapListing extends React.Component {
sortable: true,
render: (field, record) => (
{
+ e.preventDefault();
+ goToSpecifiedPath(`/map/${record.id}`);
+ }}
data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`}
>
{field}
@@ -331,7 +353,7 @@ export class MapListing extends React.Component {
};
let selection = false;
- if (!this.props.readOnly) {
+ if (!this.state.readOnly) {
selection = {
onSelectionChange: (selection) => {
this.setState({
@@ -369,14 +391,16 @@ export class MapListing extends React.Component {
renderListing() {
let createButton;
- if (!this.props.readOnly) {
+ if (!this.state.readOnly) {
createButton = (
-
-
-
+
+
+
+
+
);
}
return (
@@ -427,10 +451,3 @@ export class MapListing extends React.Component {
);
}
}
-
-MapListing.propTypes = {
- readOnly: PropTypes.bool.isRequired,
- find: PropTypes.func.isRequired,
- delete: PropTypes.func.isRequired,
- listingLimit: PropTypes.number.isRequired,
-};
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/index.js b/x-pack/plugins/maps/public/routing/routes/maps_app/index.js
new file mode 100644
index 000000000000..6b47ac6e0352
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/index.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { connect } from 'react-redux';
+import { MapsAppView } from './maps_app_view';
+import { getFlyoutDisplay, getIsFullScreen } from '../../../selectors/ui_selectors';
+import {
+ getFilters,
+ getQueryableUniqueIndexPatternIds,
+ getRefreshConfig,
+} from '../../../selectors/map_selectors';
+import {
+ replaceLayerList,
+ setGotoWithCenter,
+ setIsLayerTOCOpen,
+ setMapSettings,
+ setOpenTOCDetails,
+ setQuery,
+ setReadOnly,
+ setRefreshConfig,
+ setSelectedLayer,
+ updateFlyout,
+} from '../../../actions';
+import { FLYOUT_STATE } from '../../../reducers/ui';
+import { getMapsCapabilities } from '../../../kibana_services';
+
+function mapStateToProps(state = {}) {
+ return {
+ isFullScreen: getIsFullScreen(state),
+ nextIndexPatternIds: getQueryableUniqueIndexPatternIds(state),
+ flyoutDisplay: getFlyoutDisplay(state),
+ refreshConfig: getRefreshConfig(state),
+ filters: getFilters(state),
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ dispatchSetQuery: (refresh, filters, query, time) => {
+ dispatch(
+ setQuery({
+ filters,
+ query,
+ timeFilters: time,
+ refresh,
+ })
+ );
+ },
+ setRefreshConfig: (refreshConfig) => dispatch(setRefreshConfig(refreshConfig)),
+ replaceLayerList: (layerList) => dispatch(replaceLayerList(layerList)),
+ setGotoWithCenter: (latLonZoom) => dispatch(setGotoWithCenter(latLonZoom)),
+ setMapSettings: (mapSettings) => dispatch(setMapSettings(mapSettings)),
+ setIsLayerTOCOpen: (isLayerTOCOpen) => dispatch(setIsLayerTOCOpen(isLayerTOCOpen)),
+ setOpenTOCDetails: (openTOCDetails) => dispatch(setOpenTOCDetails(openTOCDetails)),
+ clearUi: () => {
+ dispatch(setSelectedLayer(null));
+ dispatch(updateFlyout(FLYOUT_STATE.NONE));
+ dispatch(setReadOnly(!getMapsCapabilities().save));
+ },
+ };
+}
+
+const connectedMapsAppView = connect(mapStateToProps, mapDispatchToProps)(MapsAppView);
+export { connectedMapsAppView as MapsAppView };
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.js b/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.js
new file mode 100644
index 000000000000..a17b83502e04
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { MapsAppView } from '.';
+import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
+import { getToasts } from '../../../kibana_services';
+import { i18n } from '@kbn/i18n';
+import { Redirect } from 'react-router-dom';
+
+export const LoadMapAndRender = class extends React.Component {
+ state = {
+ savedMap: null,
+ failedToLoad: false,
+ };
+
+ componentDidMount() {
+ this._isMounted = true;
+ this._loadSavedMap();
+ }
+
+ componentWillUnmount() {
+ this._isMounted = false;
+ }
+
+ async _loadSavedMap() {
+ const { savedMapId } = this.props.match.params;
+ try {
+ const savedMap = await getMapsSavedObjectLoader().get(savedMapId);
+ if (this._isMounted) {
+ this.setState({ savedMap });
+ }
+ } catch (err) {
+ if (this._isMounted) {
+ this.setState({ failedToLoad: true });
+ getToasts().addWarning({
+ title: i18n.translate('xpack.maps.loadMap.errorAttemptingToLoadSavedMap', {
+ defaultMessage: `Unable to load map`,
+ }),
+ text: `${err.message}`,
+ });
+ }
+ }
+ }
+
+ render() {
+ const { savedMap, failedToLoad } = this.state;
+ if (failedToLoad) {
+ return ;
+ }
+
+ const currentPath = this.props.match.url;
+ return savedMap ? : null;
+ }
+};
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
new file mode 100644
index 000000000000..bf92f5a33712
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
@@ -0,0 +1,476 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import 'mapbox-gl/dist/mapbox-gl.css';
+import _ from 'lodash';
+import { DEFAULT_IS_LAYER_TOC_OPEN } from '../../../reducers/ui';
+import {
+ getIndexPatternService,
+ getToasts,
+ getData,
+ getUiSettings,
+ getCoreChrome,
+} from '../../../kibana_services';
+import { copyPersistentState } from '../../../reducers/util';
+import { getInitialLayers } from '../../bootstrap/get_initial_layers';
+import rison from 'rison-node';
+import { getInitialTimeFilters } from '../../bootstrap/get_initial_time_filters';
+import { getInitialRefreshConfig } from '../../bootstrap/get_initial_refresh_config';
+import { getInitialQuery } from '../../bootstrap/get_initial_query';
+import { MapsTopNavMenu } from '../../page_elements/top_nav_menu';
+import {
+ getGlobalState,
+ updateGlobalState,
+ useGlobalStateSyncing,
+} from '../../state_syncing/global_sync';
+import { AppStateManager } from '../../state_syncing/app_state_manager';
+import { useAppStateSyncing } from '../../state_syncing/app_sync';
+import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
+import { esFilters } from '../../../../../../../src/plugins/data/public';
+import { GisMap } from '../../../connected_components/gis_map';
+
+export class MapsAppView extends React.Component {
+ _visibleSubscription = null;
+ _globalSyncUnsubscribe = null;
+ _globalSyncChangeMonitorSubscription = null;
+ _appSyncUnsubscribe = null;
+ _appStateManager = new AppStateManager();
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ indexPatterns: [],
+ prevIndexPatternIds: [],
+ initialized: false,
+ isVisible: true,
+ savedQuery: '',
+ currentPath: '',
+ initialLayerListConfig: null,
+ };
+ }
+
+ componentDidMount() {
+ const { savedMap, currentPath } = this.props;
+ this.setState({ currentPath });
+
+ getCoreChrome().docTitle.change(savedMap.title);
+ getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id);
+
+ // Init sync utils
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ this._globalSyncUnsubscribe = useGlobalStateSyncing();
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ this._appSyncUnsubscribe = useAppStateSyncing(this._appStateManager);
+ this._globalSyncChangeMonitorSubscription = getData().query.state$.subscribe(
+ this._updateFromGlobalState
+ );
+
+ // Check app state in case of refresh
+ const initAppState = this._appStateManager.getAppState();
+ this._onQueryChange(initAppState);
+ if (initAppState.savedQuery) {
+ this._updateStateFromSavedQuery(initAppState.savedQuery);
+ }
+
+ // Monitor visibility
+ this._visibleSubscription = getCoreChrome()
+ .getIsVisible$()
+ .subscribe((isVisible) => this.setState({ isVisible }));
+ this._initMap();
+ }
+
+ _initBreadcrumbUpdater = () => {
+ const { initialLayerListConfig, currentPath } = this.state;
+ updateBreadcrumbs(this.props.savedMap, initialLayerListConfig, currentPath);
+ };
+
+ componentDidUpdate(prevProps, prevState) {
+ const { currentPath: prevCurrentPath } = prevState;
+ const { currentPath, initialLayerListConfig } = this.state;
+ const { savedMap } = this.props;
+ if (savedMap && initialLayerListConfig && currentPath !== prevCurrentPath) {
+ updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
+ }
+ // TODO: Handle null when converting to TS
+ this._handleStoreChanges();
+ }
+
+ _updateFromGlobalState = ({ changes, state: globalState }) => {
+ if (!changes || !globalState) {
+ return;
+ }
+ const newState = {};
+ Object.keys(changes).forEach((key) => {
+ if (changes[key]) {
+ newState[key] = globalState[key];
+ }
+ });
+
+ this.setState(newState, () => {
+ this._appStateManager.setQueryAndFilters({
+ filters: getData().query.filterManager.getAppFilters(),
+ });
+ const { time, filters, refreshInterval } = globalState;
+ this.props.dispatchSetQuery(refreshInterval, filters, this.state.query, time);
+ });
+ };
+
+ componentWillUnmount() {
+ if (this._globalSyncUnsubscribe) {
+ this._globalSyncUnsubscribe();
+ }
+ if (this._appSyncUnsubscribe) {
+ this._appSyncUnsubscribe();
+ }
+ if (this._visibleSubscription) {
+ this._visibleSubscription.unsubscribe();
+ }
+ if (this._globalSyncChangeMonitorSubscription) {
+ this._globalSyncChangeMonitorSubscription.unsubscribe();
+ }
+
+ // Clean up app state filters
+ const { filterManager } = getData().query;
+ filterManager.filters.forEach((filter) => {
+ if (filter.$state.store === esFilters.FilterStateStore.APP_STATE) {
+ filterManager.removeFilter(filter);
+ }
+ });
+ }
+
+ _getInitialLayersFromUrlParam() {
+ const locationSplit = window.location.href.split('?');
+ if (locationSplit.length <= 1) {
+ return [];
+ }
+ const mapAppParams = new URLSearchParams(locationSplit[1]);
+ if (!mapAppParams.has('initialLayers')) {
+ return [];
+ }
+
+ try {
+ let mapInitLayers = mapAppParams.get('initialLayers');
+ if (mapInitLayers[mapInitLayers.length - 1] === '#') {
+ mapInitLayers = mapInitLayers.substr(0, mapInitLayers.length - 1);
+ }
+ return rison.decode_array(mapInitLayers);
+ } catch (e) {
+ getToasts().addWarning({
+ title: i18n.translate('xpack.maps.initialLayers.unableToParseTitle', {
+ defaultMessage: `Initial layers not added to map`,
+ }),
+ text: i18n.translate('xpack.maps.initialLayers.unableToParseMessage', {
+ defaultMessage: `Unable to parse contents of 'initialLayers' parameter. Error: {errorMsg}`,
+ values: { errorMsg: e.message },
+ }),
+ });
+ return [];
+ }
+ }
+
+ async _updateIndexPatterns(nextIndexPatternIds) {
+ const indexPatterns = [];
+ const getIndexPatternPromises = nextIndexPatternIds.map(async (indexPatternId) => {
+ try {
+ const indexPattern = await getIndexPatternService().get(indexPatternId);
+ indexPatterns.push(indexPattern);
+ } catch (err) {
+ // unable to fetch index pattern
+ }
+ });
+
+ await Promise.all(getIndexPatternPromises);
+ this.setState({
+ indexPatterns,
+ });
+ }
+
+ _handleStoreChanges = () => {
+ const { prevIndexPatternIds } = this.state;
+ const { nextIndexPatternIds } = this.props;
+
+ if (nextIndexPatternIds !== prevIndexPatternIds) {
+ this.setState({ prevIndexPatternIds: nextIndexPatternIds });
+ this._updateIndexPatterns(nextIndexPatternIds);
+ }
+ };
+
+ _getAppStateFilters = () => {
+ return this._appStateManager.getFilters() || [];
+ };
+
+ _syncAppAndGlobalState = () => {
+ const { query, time, initialized } = this.state;
+ const { refreshConfig } = this.props;
+ const { filterManager } = getData().query;
+
+ // appState
+ this._appStateManager.setQueryAndFilters({
+ query: query,
+ filters: filterManager.getAppFilters(),
+ });
+
+ // globalState
+ const refreshInterval = {
+ pause: refreshConfig.isPaused,
+ value: refreshConfig.interval,
+ };
+ updateGlobalState(
+ {
+ time: time,
+ refreshInterval,
+ filters: filterManager.getGlobalFilters(),
+ },
+ !initialized
+ );
+ this.setState({ refreshInterval });
+ };
+
+ _onQueryChange = async ({ filters, query, time, refresh }) => {
+ const { filterManager } = getData().query;
+ const { dispatchSetQuery } = this.props;
+ const newState = {};
+ let newFilters;
+ if (filters) {
+ filterManager.setFilters(filters); // Maps and merges filters
+ newFilters = filterManager.getFilters();
+ }
+ if (query) {
+ newState.query = query;
+ }
+ if (time) {
+ newState.time = time;
+ }
+ this.setState(newState, () => {
+ this._syncAppAndGlobalState();
+ dispatchSetQuery(
+ refresh,
+ newFilters || this.props.filters,
+ query || this.state.query,
+ time || this.state.time
+ );
+ });
+ };
+
+ _initQueryTimeRefresh() {
+ const { setRefreshConfig, savedMap } = this.props;
+ // TODO: Handle null when converting to TS
+ const globalState = getGlobalState();
+ const mapStateJSON = savedMap ? savedMap.mapStateJSON : undefined;
+ const newState = {
+ query: getInitialQuery({
+ mapStateJSON,
+ appState: this._appStateManager.getAppState(),
+ userQueryLanguage: getUiSettings().get('search:queryLanguage'),
+ }),
+ time: getInitialTimeFilters({
+ mapStateJSON,
+ globalState,
+ }),
+ refreshConfig: getInitialRefreshConfig({
+ mapStateJSON,
+ globalState,
+ }),
+ };
+ this.setState({ query: newState.query, time: newState.time });
+ updateGlobalState(
+ {
+ time: newState.time,
+ refreshInterval: {
+ value: newState.refreshConfig.interval,
+ pause: newState.refreshConfig.isPaused,
+ },
+ },
+ !this.state.initialized
+ );
+ setRefreshConfig(newState.refreshConfig);
+ }
+
+ _initMapAndLayerSettings() {
+ const { savedMap } = this.props;
+ // Get saved map & layer settings
+ this._initQueryTimeRefresh();
+
+ const layerList = getInitialLayers(
+ savedMap.layerListJSON,
+ this._getInitialLayersFromUrlParam()
+ );
+ this.props.replaceLayerList(layerList);
+ this.setState(
+ {
+ initialLayerListConfig: copyPersistentState(layerList),
+ savedMap,
+ },
+ this._initBreadcrumbUpdater
+ );
+ }
+
+ _updateFiltersAndDispatch = (filters) => {
+ this._onQueryChange({
+ filters,
+ });
+ };
+
+ _onRefreshChange = ({ isPaused, refreshInterval }) => {
+ const { refreshConfig } = this.props;
+ const newRefreshConfig = {
+ isPaused,
+ interval: isNaN(refreshInterval) ? refreshConfig.interval : refreshInterval,
+ };
+ this.setState({ refreshConfig: newRefreshConfig }, this._syncAppAndGlobalState);
+ this.props.setRefreshConfig(newRefreshConfig);
+ };
+
+ _updateStateFromSavedQuery(savedQuery) {
+ if (!savedQuery) {
+ this.setState({ savedQuery: '' });
+ return;
+ }
+ const { filterManager } = getData().query;
+ const savedQueryFilters = savedQuery.attributes.filters || [];
+ const globalFilters = filterManager.getGlobalFilters();
+ const allFilters = [...savedQueryFilters, ...globalFilters];
+
+ if (savedQuery.attributes.timefilter) {
+ if (savedQuery.attributes.timefilter.refreshInterval) {
+ this._onRefreshChange({
+ isPaused: savedQuery.attributes.timefilter.refreshInterval.pause,
+ refreshInterval: savedQuery.attributes.timefilter.refreshInterval.value,
+ });
+ }
+ this._onQueryChange({
+ filters: allFilters,
+ query: savedQuery.attributes.query,
+ time: savedQuery.attributes.timefilter,
+ });
+ } else {
+ this._onQueryChange({
+ filters: allFilters,
+ query: savedQuery.attributes.query,
+ });
+ }
+ }
+
+ _syncStoreAndGetFilters() {
+ const {
+ savedMap,
+ setGotoWithCenter,
+ setMapSettings,
+ setIsLayerTOCOpen,
+ setOpenTOCDetails,
+ } = this.props;
+ let savedObjectFilters = [];
+ if (savedMap.mapStateJSON) {
+ const mapState = JSON.parse(savedMap.mapStateJSON);
+ setGotoWithCenter({
+ lat: mapState.center.lat,
+ lon: mapState.center.lon,
+ zoom: mapState.zoom,
+ });
+ if (mapState.filters) {
+ savedObjectFilters = mapState.filters;
+ }
+ if (mapState.settings) {
+ setMapSettings(mapState.settings);
+ }
+ }
+
+ if (savedMap.uiStateJSON) {
+ const uiState = JSON.parse(savedMap.uiStateJSON);
+ setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN));
+ setOpenTOCDetails(_.get(uiState, 'openTOCDetails', []));
+ }
+ return savedObjectFilters;
+ }
+
+ async _initMap() {
+ const { clearUi, savedMap } = this.props;
+ // TODO: Handle null when converting to TS
+ const globalState = getGlobalState();
+ this._initMapAndLayerSettings();
+ clearUi();
+
+ const savedObjectFilters = this._syncStoreAndGetFilters(savedMap);
+ await this._onQueryChange({
+ filters: [
+ ..._.get(globalState, 'filters', []),
+ ...this._getAppStateFilters(),
+ ...savedObjectFilters,
+ ],
+ });
+ this.setState({ initialized: true });
+ }
+
+ _renderTopNav() {
+ const {
+ query,
+ time,
+ savedQuery,
+ initialLayerListConfig,
+ isVisible,
+ indexPatterns,
+ currentPath,
+ } = this.state;
+ const { savedMap, refreshConfig } = this.props;
+
+ return isVisible ? (
+ {
+ this.setState(
+ {
+ refreshConfig: newConfig,
+ },
+ callback
+ );
+ }}
+ initialLayerListConfig={initialLayerListConfig}
+ indexPatterns={indexPatterns}
+ updateFiltersAndDispatch={this._updateFiltersAndDispatch}
+ onQuerySaved={(query) => {
+ this.setState({ savedQuery: query });
+ this._appStateManager.setQueryAndFilters({ savedQuery: query });
+ this._updateStateFromSavedQuery(query);
+ }}
+ onSavedQueryUpdated={(query) => {
+ this.setState({ savedQuery: { ...query } });
+ this._appStateManager.setQueryAndFilters({ savedQuery: query });
+ this._updateStateFromSavedQuery(query);
+ }}
+ syncAppAndGlobalState={this._syncAppAndGlobalState}
+ currentPath={currentPath}
+ />
+ ) : null;
+ }
+
+ render() {
+ const { filters, isFullScreen } = this.props;
+
+ return this.state.initialized ? (
+
+ {this._renderTopNav()}
+
{`screenTitle placeholder`}
+
+ {
+ newFilters.forEach((filter) => {
+ filter.$state = { store: esFilters.FilterStateStore.APP_STATE };
+ });
+ this._updateFiltersAndDispatch([...filters, ...newFilters]);
+ }}
+ />
+
+
+ ) : null;
+ }
+}
diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.js b/x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.js
new file mode 100644
index 000000000000..19118c613080
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Subject } from 'rxjs';
+
+export class AppStateManager {
+ _query = '';
+ _savedQuery = '';
+ _filters = [];
+
+ _updated$ = new Subject();
+
+ setQueryAndFilters({ query, savedQuery, filters }) {
+ if (this._query !== query) {
+ this._query = query;
+ }
+ if (this._savedQuery !== savedQuery) {
+ this._savedQuery = savedQuery;
+ }
+ if (this._filters !== filters) {
+ this._filters = filters;
+ }
+ this._updated$.next();
+ }
+
+ getQuery() {
+ return this._query;
+ }
+
+ getFilters() {
+ return this._filters;
+ }
+
+ getAppState() {
+ return {
+ query: this._query,
+ savedQuery: this._savedQuery,
+ filters: this._filters,
+ };
+ }
+}
diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js
new file mode 100644
index 000000000000..36b20174f243
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js
@@ -0,0 +1,61 @@
+/*
+ * 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 { connectToQueryState, esFilters } from '../../../../../../src/plugins/data/public';
+import { syncState } from '../../../../../../src/plugins/kibana_utils/public';
+import { map } from 'rxjs/operators';
+import { getData } from '../../kibana_services';
+import { kbnUrlStateStorage } from '../maps_router';
+
+export function useAppStateSyncing(appStateManager) {
+ // get appStateContainer
+ // sync app filters with app state container from data.query to state container
+ const { query } = getData();
+
+ const stateContainer = {
+ get: () => ({
+ query: appStateManager.getQuery(),
+ filters: appStateManager.getFilters(),
+ }),
+ set: (state) =>
+ state && appStateManager.setQueryAndFilters({ query: state.query, filters: state.filters }),
+ state$: appStateManager._updated$.pipe(
+ map(() => ({
+ query: appStateManager.getQuery(),
+ filters: appStateManager.getFilters(),
+ }))
+ ),
+ };
+ const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(query, stateContainer, {
+ filters: esFilters.FilterStateStore.APP_STATE,
+ });
+
+ // sets up syncing app state container with url
+ const { start: startSyncingAppStateWithUrl, stop: stopSyncingAppStateWithUrl } = syncState({
+ storageKey: '_a',
+ stateStorage: kbnUrlStateStorage,
+ stateContainer,
+ });
+
+ // merge initial state from app state container and current state in url
+ const initialAppState = {
+ ...stateContainer.get(),
+ ...kbnUrlStateStorage.get('_a'),
+ };
+ // trigger state update. actually needed in case some data was in url
+ stateContainer.set(initialAppState);
+
+ // set current url to whatever is in app state container
+ kbnUrlStateStorage.set('_a', initialAppState);
+
+ // finally start syncing state containers with url
+ startSyncingAppStateWithUrl();
+
+ return () => {
+ stopSyncingQueryAppStateWithStateContainer();
+ stopSyncingAppStateWithUrl();
+ };
+}
diff --git a/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts b/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts
new file mode 100644
index 000000000000..b466f254e4d0
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { syncQueryStateWithUrl } from '../../../../../../src/plugins/data/public';
+import { getData } from '../../kibana_services';
+// @ts-ignore
+import { kbnUrlStateStorage } from '../maps_router';
+
+export function useGlobalStateSyncing() {
+ const { stop } = syncQueryStateWithUrl(getData().query, kbnUrlStateStorage);
+ return stop;
+}
+
+export function getGlobalState() {
+ return kbnUrlStateStorage.get('_g');
+}
+
+export function updateGlobalState(newState: unknown, flushUrlState = false) {
+ const globalState = getGlobalState();
+ kbnUrlStateStorage.set('_g', {
+ // @ts-ignore
+ ...globalState,
+ // @ts-ignore
+ ...newState,
+ });
+ if (flushUrlState) {
+ kbnUrlStateStorage.flush({ replace: true });
+ }
+}
diff --git a/x-pack/plugins/maps/public/routing/store_operations.js b/x-pack/plugins/maps/public/routing/store_operations.js
new file mode 100644
index 000000000000..53ebbb3328ff
--- /dev/null
+++ b/x-pack/plugins/maps/public/routing/store_operations.js
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { createMapStore } from '../reducers/store';
+
+const store = createMapStore();
+
+export const getStore = () => store;
diff --git a/x-pack/plugins/maps/server/tutorials/ems/index.ts b/x-pack/plugins/maps/server/tutorials/ems/index.ts
index 15dc403d942a..e96af89e5268 100644
--- a/x-pack/plugins/maps/server/tutorials/ems/index.ts
+++ b/x-pack/plugins/maps/server/tutorials/ems/index.ts
@@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import { TutorialsCategory } from '../../../../../../src/plugins/home/server';
+import { MAP_BASE_URL } from '../../../common/constants';
export function emsBoundariesSpecProvider({
emsLandingPageUrl,
@@ -63,7 +64,7 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou
2. Click `Add layer`, then select `Upload GeoJSON`.\n\
3. Upload the GeoJSON file and click `Import file`.',
values: {
- newMapUrl: prependBasePath('/app/maps#/map'),
+ newMapUrl: prependBasePath(MAP_BASE_URL),
},
}),
},
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
index 39b253439aff..6470fc270d0b 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
@@ -22,7 +22,8 @@ import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
import { SetQuery } from './types';
-import { MapEmbeddable } from '../../../../../../legacy/plugins/maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
index 558133104ac1..e50dcd7a8c8d 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
@@ -13,9 +13,13 @@ import { getLayerList } from './map_config';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/public';
import {
MapEmbeddable,
- RenderTooltipContentParams,
MapEmbeddableInput,
-} from '../../../../../../legacy/plugins/maps/public';
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../../../plugins/maps/public/embeddable';
+import {
+ RenderTooltipContentParams,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../../../plugins/maps/public/classes/tooltips/tooltip_property';
import * as i18n from './translations';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
import {
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx
index aef41ddaef5a..648e52e3aba3 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx
@@ -13,8 +13,10 @@ import {
SUM_OF_SERVER_BYTES,
SUM_OF_SOURCE_BYTES,
} from '../map_config';
-import { ITooltipProperty } from '../../../../../../maps/public';
-import { TooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
+import {
+ ITooltipProperty,
+ TooltipProperty,
+} from '../../../../../../maps/public/classes/tooltips/tooltip_property';
describe('LineToolTipContent', () => {
const mockFeatureProps: ITooltipProperty[] = [
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx
index 9238f7ec65a2..62d0e76287f2 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx
@@ -16,7 +16,8 @@ import {
} from '../map_config';
import * as i18n from '../translations';
-import { ITooltipProperty } from '../../../../../../maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
const FlowBadge = (styled(EuiBadge)`
height: 45px;
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.tsx
index d2b8322c8121..d3ca23f74dfd 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.tsx
@@ -17,7 +17,8 @@ import { LineToolTipContent } from './line_tool_tip_content';
import { PointToolTipContent } from './point_tool_tip_content';
import { Loader } from '../../../../common/components/loader';
import * as i18n from '../translations';
-import { ITooltipProperty } from '../../../../../../maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
export const MapToolTipComponent = ({
closeTooltip,
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx
index 36b9f44e1963..e7c9dcfdc9a5 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx
@@ -11,8 +11,10 @@ import { TestProviders } from '../../../../common/mock';
import { getEmptyStringTag } from '../../../../common/components/empty_value';
import { HostDetailsLink, IPDetailsLink } from '../../../../common/components/links';
import { FlowTarget } from '../../../../graphql/types';
-import { ITooltipProperty } from '../../../../../../maps/public';
-import { TooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
+import {
+ TooltipProperty,
+ ITooltipProperty,
+} from '../../../../../../maps/public/classes/tooltips/tooltip_property';
describe('PointToolTipContent', () => {
const mockFeatureProps: ITooltipProperty[] = [
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.tsx
index 3196e7d7a6c3..f38f3e054a64 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/point_tool_tip_content.tsx
@@ -14,7 +14,8 @@ import { DescriptionListStyled } from '../../../../common/components/page';
import { HostDetailsLink, IPDetailsLink } from '../../../../common/components/links';
import { DefaultFieldRenderer } from '../../../../timelines/components/field_renderers/field_renderers';
import { FlowTarget } from '../../../../graphql/types';
-import { ITooltipProperty } from '../../../../../../maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
interface PointToolTipContentProps {
contextId: string;
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts
index 9a49046634c3..f91fd677ba7f 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { RenderTooltipContentParams } from '../../../../../../legacy/plugins/maps/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { RenderTooltipContentParams } from '../../../../../maps/public/classes/tooltips/tooltip_property';
import { inputsModel } from '../../../common/store/inputs';
export interface IndexPatternMapping {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 8cba6432d0b9..dd15a9925b4d 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -8980,10 +8980,7 @@
"xpack.maps.addLayerPanel.footer.cancelButtonLabel": "キャンセル",
"xpack.maps.addLayerPanel.importFile": "ファイルのインポート",
"xpack.maps.aggs.defaultCountLabel": "カウント",
- "xpack.maps.appDescription": "マップアプリケーション",
"xpack.maps.appTitle": "マップ",
- "xpack.maps.badge.readOnly.text": "読み込み専用",
- "xpack.maps.badge.readOnly.tooltip": "マップを保存できませんで",
"xpack.maps.blendedVectorLayer.clusteredLayerName": "クラスター化 {displayName}",
"xpack.maps.common.esSpatialRelation.containsLabel": "contains",
"xpack.maps.common.esSpatialRelation.disjointLabel": "disjoint",
@@ -9114,7 +9111,6 @@
"xpack.maps.mapController.saveMapDescription": "マップを保存",
"xpack.maps.mapController.saveMapDisabledButtonTooltip": "保存する前に、レイヤーの変更を保存するか、キャンセルしてください",
"xpack.maps.mapController.saveSuccessMessage": "「{title}」が保存されました",
- "xpack.maps.mapController.unsavedChangesWarning": "保存されていない変更は保存されない可能性があります",
"xpack.maps.mapEmbeddableFactory.invalidLayerList": "不正な形式のレイヤーリストによりマップを読み込めません",
"xpack.maps.mapEmbeddableFactory.invalidSavedObject": "不正な形式の保存済みオブジェクトによりマップを読み込めません",
"xpack.maps.mapListing.advancedSettingsLinkText": "高度な設定",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index df69b6c91be7..196034367bcf 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -8984,10 +8984,7 @@
"xpack.maps.addLayerPanel.footer.cancelButtonLabel": "鍙栨秷",
"xpack.maps.addLayerPanel.importFile": "导入文件",
"xpack.maps.aggs.defaultCountLabel": "计数",
- "xpack.maps.appDescription": "地图应用程序",
"xpack.maps.appTitle": "Maps",
- "xpack.maps.badge.readOnly.text": "只读",
- "xpack.maps.badge.readOnly.tooltip": "无法保存地图",
"xpack.maps.blendedVectorLayer.clusteredLayerName": "集群 {displayName}",
"xpack.maps.common.esSpatialRelation.containsLabel": "contains",
"xpack.maps.common.esSpatialRelation.disjointLabel": "disjoint",
@@ -9118,7 +9115,6 @@
"xpack.maps.mapController.saveMapDescription": "保存地图",
"xpack.maps.mapController.saveMapDisabledButtonTooltip": "保存或在保存之前取消您的图层更改",
"xpack.maps.mapController.saveSuccessMessage": "已保存“{title}”",
- "xpack.maps.mapController.unsavedChangesWarning": "可能不会保存您未保存的更改",
"xpack.maps.mapEmbeddableFactory.invalidLayerList": "无法加载地图,图层列表格式不正确",
"xpack.maps.mapEmbeddableFactory.invalidSavedObject": "无法加载地图,已保存对象格式错误",
"xpack.maps.mapListing.advancedSettingsLinkText": "高级设置",
diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx b/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx
index 06cdb07bd8bc..8c971ba8c20f 100644
--- a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx
@@ -7,7 +7,11 @@
import React, { useEffect, useState, useContext, useRef } from 'react';
import uuid from 'uuid';
import styled from 'styled-components';
-import { MapEmbeddable, MapEmbeddableInput } from '../../../../../../../legacy/plugins/maps/public';
+import {
+ MapEmbeddable,
+ MapEmbeddableInput,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../../../maps/public/embeddable';
import * as i18n from './translations';
import { Location } from '../../../../../common/runtime_types';
import { getLayerList } from './map_config';
diff --git a/x-pack/test/functional/apps/maps/saved_object_management.js b/x-pack/test/functional/apps/maps/saved_object_management.js
index 62b9286d494b..810df8e99506 100644
--- a/x-pack/test/functional/apps/maps/saved_object_management.js
+++ b/x-pack/test/functional/apps/maps/saved_object_management.js
@@ -77,9 +77,9 @@ export default function ({ getPageObjects, getService }) {
it('should override query stored with map when query is provided in app state', async () => {
const currentUrl = await browser.getCurrentUrl();
- const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#'));
+ const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('/maps/'));
const appState = `_a=(query:(language:kuery,query:'machine.os.raw%20:%20"win%208"'))`;
- const urlWithQueryInAppState = `${kibanaBaseUrl}#/map/8eabdab0-144f-11e9-809f-ad25bb78262c?${appState}`;
+ const urlWithQueryInAppState = `${kibanaBaseUrl}/maps/map/8eabdab0-144f-11e9-809f-ad25bb78262c#?${appState}`;
await browser.get(urlWithQueryInAppState, true);
await PageObjects.maps.waitForLayersToLoad();
diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js
index bb07dae2c894..13d1b44cea04 100644
--- a/x-pack/test/functional/page_objects/gis_page.js
+++ b/x-pack/test/functional/page_objects/gis_page.js
@@ -5,6 +5,7 @@
*/
import _ from 'lodash';
+import { APP_ID } from '../../../plugins/maps/common/constants';
export function GisPageProvider({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['common', 'header', 'timePicker']);
@@ -159,7 +160,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
async onMapListingPage() {
log.debug(`onMapListingPage`);
- const exists = await testSubjects.exists('mapsListingPage');
+ const exists = await testSubjects.exists('mapsListingPage', { timeout: 3500 });
return exists;
}
@@ -197,7 +198,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
const onPage = await this.onMapListingPage();
if (!onPage) {
await retry.try(async () => {
- await PageObjects.common.navigateToUrl('maps', '/', { basePath: this.basePath });
+ await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/');
const onMapListingPage = await this.onMapListingPage();
if (!onMapListingPage) throw new Error('Not on map listing page.');
});
@@ -209,8 +210,8 @@ export function GisPageProvider({ getService, getPageObjects }) {
log.debug(`getMapCountWithName: ${name}`);
await this.searchForMapWithName(name);
- const links = await find.allByLinkText(name);
- return links.length;
+ const buttons = await find.allByButtonText(name);
+ return buttons.length;
}
async isSetViewPopoverOpen() {
From ae41b72f30e26ef11df18c0bfd085c985908c24e Mon Sep 17 00:00:00 2001
From: Brent Kimmel
Date: Thu, 18 Jun 2020 19:43:27 -0400
Subject: [PATCH 11/33] Merge/restyle nodes table (#69098) (#69588)
Adds panel views and drilldowns to Resolver
---
.../common/endpoint/generate_data.ts | 56 +-
.../common/endpoint/models/event.test.ts | 41 +
.../common/endpoint/models/event.ts | 92 +++
.../common/endpoint/types.ts | 24 +
.../endpoint_alerts/store/middleware.ts | 25 +-
.../resolver/models/indexed_process_tree.ts | 3 +-
.../public/resolver/models/process_event.ts | 69 ++
.../public/resolver/store/actions.ts | 55 +-
.../public/resolver/store/data/action.ts | 19 +-
.../public/resolver/store/data/reducer.ts | 16 +
.../public/resolver/store/data/selectors.ts | 15 +
.../public/resolver/store/middleware.ts | 26 +
.../public/resolver/store/reducer.ts | 28 +-
.../public/resolver/store/selectors.ts | 37 +
.../public/resolver/store/ui/selectors.ts | 16 +
.../public/resolver/types.ts | 16 +-
.../public/resolver/view/assets.tsx | 45 +-
.../public/resolver/view/index.tsx | 6 +-
.../public/resolver/view/panel.tsx | 426 ++++++-----
.../view/panels/panel_content_error.tsx | 61 ++
.../panels/panel_content_process_detail.tsx | 211 ++++++
.../panels/panel_content_process_list.tsx | 164 ++++
.../panels/panel_content_related_counts.tsx | 144 ++++
.../panels/panel_content_related_detail.tsx | 365 +++++++++
.../panels/panel_content_related_list.tsx | 247 ++++++
.../view/panels/panel_content_utilities.tsx | 90 +++
.../view/panels/process_cube_icon.tsx | 46 ++
.../resolver/view/process_event_dot.tsx | 701 +++++++++---------
.../public/resolver/view/submenu.tsx | 66 +-
29 files changed, 2557 insertions(+), 553 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/endpoint/models/event.test.ts
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_counts.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_list.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index 8c0328599677..ad140e48d4c6 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -26,6 +26,9 @@ interface EventOptions {
eventType?: string;
eventCategory?: string | string[];
processName?: string;
+ pid?: number;
+ parentPid?: number;
+ extensions?: object;
}
const Windows: OSFields[] = [
@@ -471,12 +474,36 @@ export class EndpointDocGenerator {
* @param options - Allows event field values to be specified
*/
public generateEvent(options: EventOptions = {}): EndpointEvent {
+ const processName = options.processName ? options.processName : randomProcessName();
+ const detailRecordForEventType =
+ options.extensions ||
+ ((eventCategory) => {
+ if (eventCategory === 'registry') {
+ return { registry: { key: `HKLM/Windows/Software/${this.randomString(5)}` } };
+ }
+ if (eventCategory === 'network') {
+ return {
+ network: {
+ direction: this.randomChoice(['inbound', 'outbound']),
+ forwarded_ip: `${this.randomIP()}`,
+ },
+ };
+ }
+ if (eventCategory === 'file') {
+ return { file: { path: 'C:\\My Documents\\business\\January\\processName' } };
+ }
+ if (eventCategory === 'dns') {
+ return { dns: { question: { name: `${this.randomIP()}` } } };
+ }
+ return {};
+ })(options.eventCategory);
return {
'@timestamp': options.timestamp ? options.timestamp : new Date().getTime(),
agent: { ...this.commonInfo.agent, type: 'endpoint' },
ecs: {
version: '1.4.0',
},
+ ...detailRecordForEventType,
event: {
category: options.eventCategory ? options.eventCategory : 'process',
kind: 'event',
@@ -485,9 +512,30 @@ export class EndpointDocGenerator {
},
host: this.commonInfo.host,
process: {
+ pid:
+ 'pid' in options && typeof options.pid !== 'undefined' ? options.pid : this.randomN(5000),
+ executable: `C:\\${processName}`,
+ args: `"C:\\${processName}" \\${this.randomString(3)}`,
+ code_signature: {
+ status: 'trusted',
+ subject_name: 'Microsoft',
+ },
+ hash: { md5: this.seededUUIDv4() },
entity_id: options.entityID ? options.entityID : this.randomString(10),
- parent: options.parentEntityID ? { entity_id: options.parentEntityID } : undefined,
- name: options.processName ? options.processName : randomProcessName(),
+ parent: options.parentEntityID
+ ? {
+ entity_id: options.parentEntityID,
+ pid:
+ 'parentPid' in options && typeof options.parentPid !== 'undefined'
+ ? options.parentPid
+ : this.randomN(5000),
+ }
+ : undefined,
+ name: processName,
+ },
+ user: {
+ domain: this.randomString(10),
+ name: this.randomString(10),
},
};
}
@@ -692,6 +740,8 @@ export class EndpointDocGenerator {
ancestor = this.generateEvent({
timestamp,
parentEntityID: ancestor.process.entity_id,
+ parentPid: ancestor.process.pid,
+ pid: this.randomN(5000),
});
events.push(ancestor);
timestamp = timestamp + 1000;
@@ -1117,7 +1167,7 @@ export class EndpointDocGenerator {
return [...this.randomNGenerator(255, 6)].map((x) => x.toString(16)).join('-');
}
- private randomIP(): string {
+ public randomIP(): string {
return [10, ...this.randomNGenerator(255, 3)].map((x) => x.toString()).join('.');
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts
new file mode 100644
index 000000000000..a0bf00f0274e
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { EndpointDocGenerator } from '../generate_data';
+import { descriptiveName } from './event';
+
+describe('Event descriptive names', () => {
+ let generator: EndpointDocGenerator;
+ beforeEach(() => {
+ generator = new EndpointDocGenerator('seed');
+ });
+
+ it('returns the right name for a registry event', () => {
+ const extensions = { registry: { key: `HKLM/Windows/Software/abc` } };
+ const event = generator.generateEvent({ eventCategory: 'registry', extensions });
+ expect(descriptiveName(event)).toEqual({ subject: `HKLM/Windows/Software/abc` });
+ });
+
+ it('returns the right name for a network event', () => {
+ const randomIP = `${generator.randomIP()}`;
+ const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } };
+ const event = generator.generateEvent({ eventCategory: 'network', extensions });
+ expect(descriptiveName(event)).toEqual({ subject: `${randomIP}`, descriptor: 'outbound' });
+ });
+
+ it('returns the right name for a file event', () => {
+ const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } };
+ const event = generator.generateEvent({ eventCategory: 'file', extensions });
+ expect(descriptiveName(event)).toEqual({
+ subject: 'C:\\My Documents\\business\\January\\processName',
+ });
+ });
+
+ it('returns the right name for a dns event', () => {
+ const extensions = { dns: { question: { name: `${generator.randomIP()}` } } };
+ const event = generator.generateEvent({ eventCategory: 'dns', extensions });
+ expect(descriptiveName(event)).toEqual({ subject: extensions.dns.question.name });
+ });
+});
diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts
index 9eea6bf320db..3f07bf77abf2 100644
--- a/x-pack/plugins/security_solution/common/endpoint/models/event.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts
@@ -52,3 +52,95 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
}
return event.process.parent?.entity_id;
}
+
+/**
+ * @param event The event to get the category for
+ */
+export function primaryEventCategory(event: ResolverEvent): string | undefined {
+ // Returning "Process" as a catch-all here because it seems pretty general
+ if (isLegacyEvent(event)) {
+ const legacyFullType = event.endgame.event_type_full;
+ if (legacyFullType) {
+ return legacyFullType;
+ }
+ } else {
+ const eventCategories = event.event.category;
+ const category = typeof eventCategories === 'string' ? eventCategories : eventCategories[0];
+
+ return category;
+ }
+}
+
+/**
+ * ECS event type will be things like 'creation', 'deletion', 'access', etc.
+ * see: https://www.elastic.co/guide/en/ecs/current/ecs-event.html
+ * @param event The ResolverEvent to get the ecs type for
+ */
+export function ecsEventType(event: ResolverEvent): Array {
+ if (isLegacyEvent(event)) {
+ return [event.endgame.event_subtype_full];
+ }
+ return typeof event.event.type === 'string' ? [event.event.type] : event.event.type;
+}
+
+/**
+ * #Descriptive Names For Related Events:
+ *
+ * The following section provides facilities for deriving **Descriptive Names** for ECS-compliant event data.
+ * There are drawbacks to trying to do this: It *will* require ongoing maintenance. It presents temptations to overarticulate.
+ * On balance, however, it seems that the benefit of giving the user some form of information they can recognize & scan outweighs the drawbacks.
+ */
+type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T;
+/**
+ * Based on the ECS category of the event, attempt to provide a more descriptive name
+ * (e.g. the `event.registry.key` for `registry` or the `dns.question.name` for `dns`, etc.).
+ * This function returns the data in the form of `{subject, descriptor}` where `subject` will
+ * tend to be the more distinctive term (e.g. 137.213.212.7 for a network event) and the
+ * `descriptor` can be used to present more useful/meaningful view (e.g. `inbound 137.213.212.7`
+ * in the example above).
+ * see: https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html
+ * @param event The ResolverEvent to get the descriptive name for
+ * @returns { descriptiveName } An attempt at providing a readable name to the user
+ */
+export function descriptiveName(event: ResolverEvent): { subject: string; descriptor?: string } {
+ if (isLegacyEvent(event)) {
+ return { subject: eventName(event) };
+ }
+
+ // To be somewhat defensive, we'll check for the presence of these.
+ const partialEvent: DeepPartial = event;
+
+ /**
+ * This list of attempts can be expanded/adjusted as the underlying model changes over time:
+ */
+
+ // Stable, per ECS 1.5: https://www.elastic.co/guide/en/ecs/current/ecs-allowed-values-event-category.html
+
+ if (partialEvent.network?.forwarded_ip) {
+ return {
+ subject: String(partialEvent.network?.forwarded_ip),
+ descriptor: String(partialEvent.network?.direction),
+ };
+ }
+
+ if (partialEvent.file?.path) {
+ return {
+ subject: String(partialEvent.file?.path),
+ };
+ }
+
+ // Extended categories (per ECS 1.5):
+ const pathOrKey = partialEvent.registry?.path || partialEvent.registry?.key;
+ if (pathOrKey) {
+ return {
+ subject: String(pathOrKey),
+ };
+ }
+
+ if (partialEvent.dns?.question?.name) {
+ return { subject: String(partialEvent.dns?.question?.name) };
+ }
+
+ // Fall back on entityId if we can't fish a more descriptive name out.
+ return { subject: entityId(event) };
+}
diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts
index bd4d8372497f..0b93af741da2 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types.ts
@@ -444,14 +444,38 @@ export interface EndpointEvent {
kind: string;
};
host: Host;
+ network?: {
+ direction: unknown;
+ forwarded_ip: unknown;
+ };
+ dns?: {
+ question: { name: unknown };
+ };
process: {
entity_id: string;
name: string;
+ executable?: string;
+ args?: string;
+ code_signature?: {
+ status?: string;
+ subject_name: string;
+ };
+ pid?: number;
+ hash?: {
+ md5: string;
+ };
parent?: {
entity_id: string;
name?: string;
+ pid?: number;
};
};
+ user?: {
+ domain?: string;
+ name: string;
+ };
+ file?: { path: unknown };
+ registry?: { path: unknown; key: unknown };
}
export type ResolverEvent = EndpointEvent | LegacyEndpointEvent;
diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts b/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts
index cd7ed93d22d9..ae5d36ce0d1f 100644
--- a/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts
@@ -13,11 +13,12 @@ import {
} from '../../../common/endpoint_alerts/types';
import { ImmutableMiddlewareFactory } from '../../common/store';
import { cloneHttpFetchQuery } from '../../common/utils/clone_http_fetch_query';
+
import {
isOnAlertPage,
apiQueryParams,
- hasSelectedAlert,
uiQueryParams,
+ hasSelectedAlert,
isAlertPageTabChange,
} from './selectors';
@@ -25,6 +26,26 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory
coreStart,
depsStart
) => {
+ let lastSelectedAlert: string | null = null;
+ /**
+ * @returns true once per change of `selectedAlert` in query params.
+ *
+ * As opposed to `hasSelectedAlert` which always returns true if the alert is present
+ * query params, which can cause unnecessary requests and re-renders in some cases.
+ */
+ const selectedAlertHasChanged = (params: ReturnType): boolean => {
+ const { selected_alert: selectedAlert } = params;
+ const shouldNotChange = selectedAlert === lastSelectedAlert;
+ if (shouldNotChange) {
+ return false;
+ }
+ if (typeof selectedAlert !== 'string') {
+ return false;
+ }
+ lastSelectedAlert = selectedAlert;
+ return true;
+ };
+
async function fetchIndexPatterns(): Promise {
const { indexPatterns } = depsStart.data;
const fields = await indexPatterns.getFieldsForWildcard({
@@ -50,7 +71,7 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory
});
api.dispatch({ type: 'serverReturnedAlertsData', payload: listResponse });
- if (hasSelectedAlert(state)) {
+ if (hasSelectedAlert(state) && selectedAlertHasChanged(uiQueryParams(state))) {
const uiParams = uiQueryParams(state);
const detailsResponse: AlertDetails = await coreStart.http.get(
`/api/endpoint/alerts/${uiParams.selected_alert}`
diff --git a/x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree.ts b/x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree.ts
index 3102b450e54f..db00ca2d5996 100644
--- a/x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree.ts
+++ b/x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree.ts
@@ -55,9 +55,8 @@ export function factory(processes: ResolverEvent[]): IndexedProcessTree {
currentProcessAdjacencyMap.parent = uniqueParentPid;
}
} else {
- idToChildren.set(uniqueParentPid, [process]);
-
if (uniqueParentPid) {
+ idToChildren.set(uniqueParentPid, [process]);
/**
* Get the parent's map, otherwise set an empty one
*/
diff --git a/x-pack/plugins/security_solution/public/resolver/models/process_event.ts b/x-pack/plugins/security_solution/public/resolver/models/process_event.ts
index 038e5b90b217..1094fee6da24 100644
--- a/x-pack/plugins/security_solution/public/resolver/models/process_event.ts
+++ b/x-pack/plugins/security_solution/public/resolver/models/process_event.ts
@@ -78,6 +78,17 @@ export function uniquePidForProcess(passedEvent: ResolverEvent): string {
}
}
+/**
+ * Returns the pid for the process on the host
+ */
+export function processPid(passedEvent: ResolverEvent): number | undefined {
+ if (event.isLegacyEvent(passedEvent)) {
+ return passedEvent.endgame.pid;
+ } else {
+ return passedEvent.process.pid;
+ }
+}
+
/**
* Returns the process event's parent pid
*/
@@ -88,3 +99,61 @@ export function uniqueParentPidForProcess(passedEvent: ResolverEvent): string |
return passedEvent.process.parent?.entity_id;
}
}
+
+/**
+ * Returns the process event's parent pid
+ */
+export function processParentPid(passedEvent: ResolverEvent): number | undefined {
+ if (event.isLegacyEvent(passedEvent)) {
+ return passedEvent.endgame.ppid;
+ } else {
+ return passedEvent.process.parent?.pid;
+ }
+}
+
+/**
+ * Returns the process event's path on its host
+ */
+export function processPath(passedEvent: ResolverEvent): string | undefined {
+ if (event.isLegacyEvent(passedEvent)) {
+ return passedEvent.endgame.process_path;
+ } else {
+ return passedEvent.process.executable;
+ }
+}
+
+/**
+ * Returns the username for the account that ran the process
+ */
+export function userInfoForProcess(
+ passedEvent: ResolverEvent
+): { user?: string; domain?: string } | undefined {
+ return passedEvent.user;
+}
+
+/**
+ * Returns the MD5 hash for the `passedEvent` param, or undefined if it can't be located
+ * @param {ResolverEvent} passedEvent The `ResolverEvent` to get the MD5 value for
+ * @returns {string | undefined} The MD5 string for the event
+ */
+export function md5HashForProcess(passedEvent: ResolverEvent): string | undefined {
+ if (event.isLegacyEvent(passedEvent)) {
+ // There is not currently a key for this on Legacy event types
+ return undefined;
+ }
+ return passedEvent?.process?.hash?.md5;
+}
+
+/**
+ * Returns the command line path and arguments used to run the `passedEvent` if any
+ *
+ * @param {ResolverEvent} passedEvent The `ResolverEvent` to get the arguemnts value for
+ * @returns {string | undefined} The arguments (including the path) used to run the process
+ */
+export function argsForProcess(passedEvent: ResolverEvent): string | undefined {
+ if (event.isLegacyEvent(passedEvent)) {
+ // There is not currently a key for this on Legacy event types
+ return undefined;
+ }
+ return passedEvent?.process?.args;
+}
diff --git a/x-pack/plugins/security_solution/public/resolver/store/actions.ts b/x-pack/plugins/security_solution/public/resolver/store/actions.ts
index 0963118ce14b..c633d791e8bf 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/actions.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/actions.ts
@@ -24,6 +24,35 @@ interface UserBroughtProcessIntoView {
};
}
+/**
+ * Dispatched to notify state that a different panel needs to be displayed
+ */
+interface AppDisplayedDifferentPanel {
+ readonly type: 'appDisplayedDifferentPanel';
+ /**
+ * The name of the panel to display
+ */
+ readonly payload: string;
+}
+
+/**
+ * When an examination of query params in the UI indicates that state needs to
+ * be updated to reflect the new selection
+ */
+interface AppDetectedNewIdFromQueryParams {
+ readonly type: 'appDetectedNewIdFromQueryParams';
+ readonly payload: {
+ /**
+ * Used to identify the process the process that should be synced with state.
+ */
+ readonly process: ResolverEvent;
+ /**
+ * The time (since epoch in milliseconds) when the action was dispatched.
+ */
+ readonly time: number;
+ };
+}
+
/**
* Used when the alert list selects an alert and the flyout shows resolver.
*/
@@ -45,12 +74,21 @@ interface AppRequestedResolverData {
}
/**
- * The action dispatched when the app requests related event data for one or more
- * subjects (whose ids should be included as an array @ `payload`)
+ * The action dispatched when the app requests related event data for one
+ * subject (whose entity_id should be included as `payload`)
*/
interface UserRequestedRelatedEventData {
readonly type: 'userRequestedRelatedEventData';
- readonly payload: ResolverEvent;
+ readonly payload: string;
+}
+
+/**
+ * The action dispatched when the app requests related event data for one
+ * subject (whose entity_id should be included as `payload`)
+ */
+interface AppDetectedMissingEventData {
+ readonly type: 'appDetectedMissingEventData';
+ readonly payload: string;
}
/**
@@ -80,9 +118,13 @@ interface UserSelectedResolverNode {
readonly type: 'userSelectedResolverNode';
readonly payload: {
/**
- * Used to identify the process node that the user selected
+ * The HTML ID used to identify the process node's element that the user selected
*/
readonly nodeId: string;
+ /**
+ * The process entity_id for the process the node represents
+ */
+ readonly selectedProcessId: string;
};
}
@@ -118,4 +160,7 @@ export type ResolverAction =
| UserSelectedResolverNode
| UserRequestedRelatedEventData
| UserSelectedRelatedEventCategory
- | UserSelectedRelatedAlerts;
+ | UserSelectedRelatedAlerts
+ | AppDetectedNewIdFromQueryParams
+ | AppDisplayedDifferentPanel
+ | AppDetectedMissingEventData;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
index 96552fbed620..fbeeefe1ab9f 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
@@ -4,7 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
+import {
+ ResolverEvent,
+ ResolverNodeStats,
+ ResolverRelatedEvents,
+} from '../../../../common/endpoint/types';
interface ServerReturnedResolverData {
readonly type: 'serverReturnedResolverData';
@@ -21,10 +25,19 @@ interface ServerFailedToReturnResolverData {
*/
interface ServerFailedToReturnRelatedEventData {
readonly type: 'serverFailedToReturnRelatedEventData';
- readonly payload: ResolverEvent;
+ readonly payload: string;
+}
+
+/**
+ * When related events are returned from the server
+ */
+interface ServerReturnedRelatedEventData {
+ readonly type: 'serverReturnedRelatedEventData';
+ readonly payload: ResolverRelatedEvents;
}
export type DataAction =
| ServerReturnedResolverData
| ServerFailedToReturnResolverData
- | ServerFailedToReturnRelatedEventData;
+ | ServerFailedToReturnRelatedEventData
+ | ServerReturnedRelatedEventData;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts
index 0c29411c316d..3e897a91a74c 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts
@@ -11,6 +11,8 @@ function initialState(): DataState {
return {
results: [],
relatedEventsStats: new Map(),
+ relatedEvents: new Map(),
+ relatedEventsReady: new Map(),
isLoading: false,
hasError: false,
};
@@ -36,6 +38,20 @@ export const dataReducer: Reducer = (state = initialS
...state,
hasError: true,
};
+ } else if (
+ action.type === 'userRequestedRelatedEventData' ||
+ action.type === 'appDetectedMissingEventData'
+ ) {
+ return {
+ ...state,
+ relatedEventsReady: new Map([...state.relatedEventsReady, [action.payload, false]]),
+ };
+ } else if (action.type === 'serverReturnedRelatedEventData') {
+ return {
+ ...state,
+ relatedEventsReady: new Map([...state.relatedEventsReady, [action.payload.entityID, true]]),
+ relatedEvents: new Map([...state.relatedEvents, [action.payload.entityID, action.payload]]),
+ };
} else {
return state;
}
diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts
index 672b3fb2c729..2873993cc645 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts
@@ -435,6 +435,21 @@ export function relatedEventsStats(data: DataState) {
return data.relatedEventsStats;
}
+/**
+ * returns {Map} a map of entity_ids to related event data.
+ */
+export function relatedEventsByEntityId(data: DataState) {
+ return data.relatedEvents;
+}
+
+/**
+ * returns {Map} a map of entity_ids to booleans indicating if it is waiting on related event
+ * A value of `undefined` can be interpreted as `not yet requested`
+ */
+export function relatedEventsReady(data: DataState) {
+ return data.relatedEventsReady;
+}
+
export const processAdjacencies = createSelector(
indexedProcessTree,
graphableProcesses,
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
index 079eecf0315a..7f6f58dac715 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
@@ -14,6 +14,7 @@ import {
ResolverAncestry,
LifecycleNode,
ResolverNodeStats,
+ ResolverRelatedEvents,
} from '../../../common/endpoint/types';
import * as event from '../../../common/endpoint/models/event';
@@ -92,6 +93,31 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => {
});
}
}
+ } else if (
+ (action.type === 'userRequestedRelatedEventData' ||
+ action.type === 'appDetectedMissingEventData') &&
+ context
+ ) {
+ const entityIdToFetchFor = action.payload;
+ let result: ResolverRelatedEvents;
+ try {
+ result = await context.services.http.get(
+ `/api/endpoint/resolver/${entityIdToFetchFor}/events`,
+ {
+ query: { events: 100 },
+ }
+ );
+
+ api.dispatch({
+ type: 'serverReturnedRelatedEventData',
+ payload: result,
+ });
+ } catch (e) {
+ api.dispatch({
+ type: 'serverFailedToReturnRelatedEventData',
+ payload: action.payload,
+ });
+ }
}
};
};
diff --git a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
index 82206d77f834..77dffd79ea09 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
@@ -19,7 +19,12 @@ import { uniquePidForProcess } from '../models/process_event';
const resolverNodeIdGenerator = htmlIdGenerator('resolverNode');
const uiReducer: Reducer = (
- uiState = { activeDescendantId: null, selectedDescendantId: null },
+ uiState = {
+ activeDescendantId: null,
+ selectedDescendantId: null,
+ processEntityIdOfSelectedDescendant: null,
+ panelToDisplay: null,
+ },
action
) => {
if (action.type === 'userFocusedOnResolverNode') {
@@ -31,17 +36,29 @@ const uiReducer: Reducer = (
return {
...uiState,
selectedDescendantId: action.payload.nodeId,
+ processEntityIdOfSelectedDescendant: action.payload.selectedProcessId,
};
- } else if (action.type === 'userBroughtProcessIntoView') {
+ } else if (action.type === 'appDisplayedDifferentPanel') {
+ return {
+ ...uiState,
+ panelToDisplay: action.payload,
+ };
+ } else if (
+ action.type === 'userBroughtProcessIntoView' ||
+ action.type === 'appDetectedNewIdFromQueryParams'
+ ) {
/**
* This action has a process payload (instead of a processId), so we use
* `uniquePidForProcess` and `resolverNodeIdGenerator` to resolve the determinant
* html id of the node being brought into view.
*/
- const processNodeId = resolverNodeIdGenerator(uniquePidForProcess(action.payload.process));
+ const processEntityId = uniquePidForProcess(action.payload.process);
+ const processNodeId = resolverNodeIdGenerator(processEntityId);
return {
...uiState,
activeDescendantId: processNodeId,
+ selectedDescendantId: processNodeId,
+ processEntityIdOfSelectedDescendant: processEntityId,
};
} else {
return uiState;
@@ -56,7 +73,10 @@ const concernReducers = combineReducers({
export const resolverReducer: Reducer = (state, action) => {
const nextState = concernReducers(state, action);
- if (action.type === 'userBroughtProcessIntoView') {
+ if (
+ action.type === 'userBroughtProcessIntoView' ||
+ action.type === 'appDetectedNewIdFromQueryParams'
+ ) {
return animateProcessIntoView(nextState, action.payload.time, action.payload.process);
} else {
return nextState;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
index 270a84d24a99..bff30c62864f 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
@@ -68,6 +68,22 @@ export const relatedEventsStats = composeSelectors(
dataSelectors.relatedEventsStats
);
+/**
+ * Map of related events... by entity id
+ */
+export const relatedEventsByEntityId = composeSelectors(
+ dataStateSelector,
+ dataSelectors.relatedEventsByEntityId
+);
+
+/**
+ * Entity ids to booleans for waiting status
+ */
+export const relatedEventsReady = composeSelectors(
+ dataStateSelector,
+ dataSelectors.relatedEventsReady
+);
+
/**
* Returns the id of the "current" tree node (fake-focused)
*/
@@ -84,6 +100,19 @@ export const uiSelectedDescendantId = composeSelectors(
uiSelectors.selectedDescendantId
);
+/**
+ * Returns the entity_id of the "selected" tree node's process
+ */
+export const uiSelectedDescendantProcessId = composeSelectors(
+ uiStateSelector,
+ uiSelectors.selectedDescendantProcessId
+);
+
+/**
+ * The current panel to display
+ */
+export const currentPanelView = composeSelectors(uiStateSelector, uiSelectors.currentPanelView);
+
/**
* Returns the camera state from within ResolverState
*/
@@ -115,6 +144,14 @@ export const isLoading = composeSelectors(dataStateSelector, dataSelectors.isLoa
*/
export const hasError = composeSelectors(dataStateSelector, dataSelectors.hasError);
+/**
+ * An array containing all the processes currently in the Resolver than can be graphed
+ */
+export const graphableProcesses = composeSelectors(
+ dataStateSelector,
+ dataSelectors.graphableProcesses
+);
+
/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.
diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
index 196e834c406b..bddc7d34abf1 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
@@ -28,3 +28,19 @@ export const selectedDescendantId = createSelector(
return selectedDescendantId;
}
);
+
+/**
+ * id of the currently "selected" tree node
+ */
+export const selectedDescendantProcessId = createSelector(
+ (uiState: ResolverUIState) => uiState,
+ /* eslint-disable no-shadow */
+ ({ processEntityIdOfSelectedDescendant }: ResolverUIState) => {
+ return processEntityIdOfSelectedDescendant;
+ }
+);
+
+// Select the current panel to be displayed
+export const currentPanelView = (uiState: ResolverUIState) => {
+ return uiState.panelToDisplay;
+};
diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts
index 765b2b2a26ad..a48f3b59b0f6 100644
--- a/x-pack/plugins/security_solution/public/resolver/types.ts
+++ b/x-pack/plugins/security_solution/public/resolver/types.ts
@@ -8,7 +8,11 @@ import { Store } from 'redux';
import { ResolverAction } from './store/actions';
export { ResolverAction } from './store/actions';
-import { ResolverEvent, ResolverNodeStats } from '../../common/endpoint/types';
+import {
+ ResolverEvent,
+ ResolverNodeStats,
+ ResolverRelatedEvents,
+} from '../../common/endpoint/types';
/**
* Redux state for the Resolver feature. Properties on this interface are populated via multiple reducers using redux's `combineReducers`.
@@ -42,6 +46,14 @@ export interface ResolverUIState {
* The ID attribute of the resolver's currently selected descendant.
*/
readonly selectedDescendantId: string | null;
+ /**
+ * The entity_id of the process for the resolver's currently selected descendant.
+ */
+ readonly processEntityIdOfSelectedDescendant: string | null;
+ /**
+ * Which panel the ui should display
+ */
+ readonly panelToDisplay: string | null;
}
/**
@@ -136,6 +148,8 @@ export type CameraState = {
export interface DataState {
readonly results: readonly ResolverEvent[];
readonly relatedEventsStats: Map;
+ readonly relatedEvents: Map;
+ readonly relatedEventsReady: Map;
isLoading: boolean;
hasError: boolean;
}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/assets.tsx b/x-pack/plugins/security_solution/public/resolver/view/assets.tsx
index 150ab3d93a8c..82f969b755b2 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/assets.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/assets.tsx
@@ -12,6 +12,9 @@ import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { useUiSetting } from '../../common/lib/kibana';
import { DEFAULT_DARK_MODE } from '../../../common/constants';
+import { ResolverEvent } from '../../../common/endpoint/types';
+import * as processModel from '../models/process_event';
+import { ResolverProcessType } from '../types';
type ResolverColorNames =
| 'descriptionText'
@@ -405,7 +408,37 @@ export const SymbolDefinitions = styled(SymbolDefinitionsComponent)`
height: 0;
`;
-export const useResolverTheme = (): { colorMap: ColorMap; nodeAssets: NodeStyleMap } => {
+const processTypeToCube: Record = {
+ processCreated: 'runningProcessCube',
+ processRan: 'runningProcessCube',
+ processTerminated: 'terminatedProcessCube',
+ unknownProcessEvent: 'runningProcessCube',
+ processCausedAlert: 'runningTriggerCube',
+ unknownEvent: 'runningProcessCube',
+};
+
+/**
+ * This will return which type the ResolverEvent will display as in the Node component
+ * it will be something like 'runningProcessCube' or 'terminatedProcessCube'
+ *
+ * @param processEvent {ResolverEvent} the event to get the Resolver Component Node type of
+ */
+export function nodeType(processEvent: ResolverEvent): keyof NodeStyleMap {
+ const processType = processModel.eventType(processEvent);
+ if (processType in processTypeToCube) {
+ return processTypeToCube[processType];
+ }
+ return 'runningProcessCube';
+}
+
+/**
+ * A hook to bring Resolver theming information into components.
+ */
+export const useResolverTheme = (): {
+ colorMap: ColorMap;
+ nodeAssets: NodeStyleMap;
+ cubeAssetsForNode: (arg0: ResolverEvent) => NodeStyleConfig;
+} => {
const isDarkMode = useUiSetting(DEFAULT_DARK_MODE);
const theme = isDarkMode ? euiThemeAmsterdamDark : euiThemeAmsterdamLight;
@@ -478,7 +511,15 @@ export const useResolverTheme = (): { colorMap: ColorMap; nodeAssets: NodeStyleM
},
};
- return { colorMap, nodeAssets };
+ /**
+ * Export assets to reuse symbols/icons in other places in the app (e.g. tables, etc.)
+ * @param processEvent : The process event to fetch node assets for
+ */
+ function cubeAssetsForNode(processEvent: ResolverEvent) {
+ return nodeAssets[nodeType(processEvent)];
+ }
+
+ return { colorMap, nodeAssets, cubeAssetsForNode };
};
export const calculateResolverFontSize = (
diff --git a/x-pack/plugins/security_solution/public/resolver/view/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/index.tsx
index 0e15cd5c4e1d..9dfc9a45fafe 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/index.tsx
@@ -53,9 +53,9 @@ const StyledResolver = styled.div`
const StyledPanel = styled(Panel)`
position: absolute;
- left: 1em;
- top: 1em;
- max-height: calc(100% - 2em);
+ left: 0;
+ top: 0;
+ bottom: 0;
overflow: auto;
width: 25em;
max-width: 50%;
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx
index 2b67a4ac16d4..4bef2f4d2a10 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx
@@ -3,191 +3,283 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { memo, useCallback, useMemo, useContext } from 'react';
-import {
- EuiPanel,
- EuiBadge,
- EuiBasicTableColumn,
- EuiTitle,
- EuiHorizontalRule,
- EuiInMemoryTable,
-} from '@elastic/eui';
-import euiVars from '@elastic/eui/dist/eui_theme_light.json';
+
+import React, {
+ memo,
+ useCallback,
+ useMemo,
+ useContext,
+ useLayoutEffect,
+ useState,
+ useEffect,
+} from 'react';
import { useSelector } from 'react-redux';
-import { i18n } from '@kbn/i18n';
-import { SideEffectContext } from './side_effect_context';
-import { ResolverEvent } from '../../../common/endpoint/types';
-import * as event from '../../../common/endpoint/models/event';
-import { useResolverDispatch } from './use_resolver_dispatch';
+import { useHistory } from 'react-router-dom';
+// eslint-disable-next-line import/no-nodejs-modules
+import querystring from 'querystring';
+import { EuiPanel } from '@elastic/eui';
+import { displayNameRecord } from './process_event_dot';
import * as selectors from '../store/selectors';
+import { useResolverDispatch } from './use_resolver_dispatch';
+import * as event from '../../../common/endpoint/models/event';
+import { ResolverEvent } from '../../../common/endpoint/types';
+import { SideEffectContext } from './side_effect_context';
+import { ProcessEventListNarrowedByType } from './panels/panel_content_related_list';
+import { EventCountsForProcess } from './panels/panel_content_related_counts';
+import { ProcessDetails } from './panels/panel_content_process_detail';
+import { ProcessListWithCounts } from './panels/panel_content_process_list';
+import { RelatedEventDetail } from './panels/panel_content_related_detail';
+import { CrumbInfo } from './panels/panel_content_utilities';
-const HorizontalRule = memo(function HorizontalRule() {
- return (
-
- );
-});
-
-export const Panel = memo(function Event({ className }: { className?: string }) {
- interface ProcessTableView {
- name: string;
- timestamp?: Date;
- event: ResolverEvent;
- }
+/**
+ * The team decided to use this table to determine which breadcrumbs/view to display:
+ *
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | all processes/default | null | null |
+ * | process detail | entity_id of process | null |
+ * | relateds count by type | entity_id of process | 'all' |
+ * | relateds list 1 type | entity_id of process | valid related event type |
+ * | related event detail | event_id of related event | entity_id of process |
+ *
+ * This component implements the strategy laid out above by determining the "right" view and doing some other housekeeping e.g. effects to keep the UI-selected node in line with what's indicated by the URL parameters.
+ *
+ * @returns {JSX.Element} The "right" table content to show based on the query params as described above
+ */
+const PanelContent = memo(function PanelContent() {
+ const history = useHistory();
+ const urlSearch = history.location.search;
+ const dispatch = useResolverDispatch();
- const { processNodePositions } = useSelector(selectors.processNodePositionsAndEdgeLineSegments);
- const { timestamp } = useContext(SideEffectContext);
+ const queryParams: CrumbInfo = useMemo(() => {
+ return { crumbId: '', crumbEvent: '', ...querystring.parse(urlSearch.slice(1)) };
+ }, [urlSearch]);
- const processTableView: ProcessTableView[] = useMemo(
- () =>
- [...processNodePositions.keys()].map((processEvent) => {
- let dateTime;
- const eventTime = event.eventTimestamp(processEvent);
- const name = event.eventName(processEvent);
- if (eventTime) {
- const date = new Date(eventTime);
- if (isFinite(date.getTime())) {
- dateTime = date;
- }
- }
- return {
- name,
- timestamp: dateTime,
- event: processEvent,
- };
- }),
- [processNodePositions]
- );
+ const graphableProcesses = useSelector(selectors.graphableProcesses);
+ const graphableProcessEntityIds = useMemo(() => {
+ return new Set(graphableProcesses.map(event.entityId));
+ }, [graphableProcesses]);
+ // The entity id in query params of a graphable process (or false if none is found)
+ // For 1 case (the related detail, see below), the process id will be in crumbEvent instead of crumbId
+ const idFromParams = useMemo(() => {
+ if (graphableProcessEntityIds.has(queryParams.crumbId)) {
+ return queryParams.crumbId;
+ }
+ if (graphableProcessEntityIds.has(queryParams.crumbEvent)) {
+ return queryParams.crumbEvent;
+ }
+ return '';
+ }, [queryParams, graphableProcessEntityIds]);
- const formatter = new Intl.DateTimeFormat(i18n.getLocale(), {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- });
+ // The "selected" node (and its corresponding event) in the tree control.
+ // It may need to be synchronized with the ID indicated as selected via the `idFromParams`
+ // memo above. When this is the case, it is handled by the layout effect below.
+ const selectedDescendantProcessId = useSelector(selectors.uiSelectedDescendantProcessId);
+ const uiSelectedEvent = useMemo(() => {
+ return graphableProcesses.find((evt) => event.entityId(evt) === selectedDescendantProcessId);
+ }, [graphableProcesses, selectedDescendantProcessId]);
- const dispatch = useResolverDispatch();
+ // Until an event is dispatched during update, the event indicated as selected by params may
+ // be different than the one in state.
+ const paramsSelectedEvent = useMemo(() => {
+ return graphableProcesses.find((evt) => event.entityId(evt) === idFromParams);
+ }, [graphableProcesses, idFromParams]);
+ const { timestamp } = useContext(SideEffectContext);
+ const [lastUpdatedProcess, setLastUpdatedProcess] = useState(null);
- const handleBringIntoViewClick = useCallback(
- (processTableViewItem) => {
+ /**
+ * When the ui-selected node is _not_ the one indicated by the query params, but the id from params _is_ in the current tree,
+ * dispatch a selection action to amend the UI state to hold the query id as "selected".
+ * This is to cover cases where users e.g. share links to reconstitute a Resolver state or
+ * an effect pushes a new process id to the query params.
+ */
+ useLayoutEffect(() => {
+ if (
+ paramsSelectedEvent &&
+ // Check state to ensure we don't dispatch this in a way that causes unnecessary re-renders, or disrupts animation:
+ paramsSelectedEvent !== lastUpdatedProcess &&
+ paramsSelectedEvent !== uiSelectedEvent
+ ) {
+ setLastUpdatedProcess(paramsSelectedEvent);
dispatch({
- type: 'userBroughtProcessIntoView',
+ type: 'appDetectedNewIdFromQueryParams',
payload: {
time: timestamp(),
- process: processTableViewItem.event,
+ process: paramsSelectedEvent,
},
});
+ }
+ }, [dispatch, uiSelectedEvent, paramsSelectedEvent, lastUpdatedProcess, timestamp]);
+
+ /**
+ * This updates the breadcrumb nav and the panel view. It's supplied to each
+ * panel content view to allow them to dispatch transitions to each other.
+ */
+ const pushToQueryParams = useCallback(
+ (newCrumbs: CrumbInfo) => {
+ // Construct a new set of params from the current set (minus empty params)
+ // by assigning the new set of params provided in `newCrumbs`
+ const crumbsToPass = {
+ ...querystring.parse(urlSearch.slice(1)),
+ ...newCrumbs,
+ };
+
+ // If either was passed in as empty, remove it from the record
+ if (crumbsToPass.crumbId === '') {
+ delete crumbsToPass.crumbId;
+ }
+ if (crumbsToPass.crumbEvent === '') {
+ delete crumbsToPass.crumbEvent;
+ }
+
+ const relativeURL = { search: querystring.stringify(crumbsToPass) };
+ // We probably don't want to nuke the user's history with a huge
+ // trail of these, thus `.replace` instead of `.push`
+ return history.replace(relativeURL);
},
- [dispatch, timestamp]
+ [history, urlSearch]
);
- const columns = useMemo>>(
- () => [
- {
- field: 'name',
- name: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.processNameTitle',
- {
- defaultMessage: 'Process Name',
- }
- ),
- sortable: true,
- truncateText: true,
- render(name: string) {
- return name === '' ? (
-
- {i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.table.row.valueMissingDescription',
- {
- defaultMessage: 'Value is missing',
- }
- )}
-
- ) : (
- name
- );
- },
- },
- {
- field: 'timestamp',
- name: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.timestampTitle',
- {
- defaultMessage: 'Timestamp',
- }
- ),
- dataType: 'date',
- sortable: true,
- render(eventDate?: Date) {
- return eventDate ? (
- formatter.format(eventDate)
- ) : (
-
- {i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.timestampInvalidLabel',
- {
- defaultMessage: 'invalid',
- }
- )}
-
- );
- },
- },
- {
- name: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.actionsTitle',
- {
- defaultMessage: 'Actions',
- }
- ),
- actions: [
- {
- name: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.actions.bringIntoViewButtonLabel',
- {
- defaultMessage: 'Bring into view',
- }
- ),
- description: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.tabel.row.bringIntoViewLabel',
- {
- defaultMessage: 'Bring the process into view on the map.',
- }
- ),
- type: 'icon',
- icon: 'flag',
- onClick: handleBringIntoViewClick,
- },
- ],
- },
- ],
- [formatter, handleBringIntoViewClick]
- );
+ // GO JONNY GO
+ const relatedEventStats = useSelector(selectors.relatedEventsStats);
+ const { crumbId, crumbEvent } = queryParams;
+ const relatedStatsForIdFromParams = useMemo(() => {
+ if (idFromParams) {
+ return relatedEventStats.get(idFromParams);
+ }
+ return undefined;
+ }, [relatedEventStats, idFromParams]);
+
+ /**
+ * Determine which set of breadcrumbs to display based on the query parameters
+ * for the table & breadcrumb nav.
+ *
+ */
+ const panelToShow = useMemo(() => {
+ if (crumbEvent === '' && crumbId === '') {
+ /**
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | all processes/default | null | null |
+ */
+ return 'processListWithCounts';
+ }
+
+ if (graphableProcessEntityIds.has(crumbId)) {
+ /**
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | process detail | entity_id of process | null |
+ */
+ if (crumbEvent === '' && uiSelectedEvent) {
+ return 'processDetails';
+ }
+
+ /**
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | relateds count by type | entity_id of process | 'all' |
+ */
+
+ if (crumbEvent === 'all' && uiSelectedEvent) {
+ return 'eventCountsForProcess';
+ }
+
+ /**
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | relateds list 1 type | entity_id of process | valid related event type |
+ */
+
+ if (crumbEvent in displayNameRecord && uiSelectedEvent) {
+ return 'processEventListNarrowedByType';
+ }
+ }
+
+ if (graphableProcessEntityIds.has(crumbEvent)) {
+ /**
+ * | Crumb/Table | &crumbId | &crumbEvent |
+ * | :--------------------- | :------------------------- | :---------------------- |
+ * | related event detail | event_id of related event | entity_id of process |
+ */
+ return 'relatedEventDetail';
+ }
+
+ // The default 'Event List' / 'List of all processes' view
+ return 'processListWithCounts';
+ }, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]);
+
+ useEffect(() => {
+ // dispatch `appDisplayedDifferentPanel` to sync state with which panel gets displayed
+ dispatch({
+ type: 'appDisplayedDifferentPanel',
+ payload: panelToShow,
+ });
+ }, [panelToShow, dispatch]);
+
+ const currentPanelView = useSelector(selectors.currentPanelView);
+
+ const panelInstance = useMemo(() => {
+ if (currentPanelView === 'processDetails') {
+ return (
+
+ );
+ }
+
+ if (currentPanelView === 'eventCountsForProcess') {
+ return (
+
+ );
+ }
+
+ if (currentPanelView === 'processEventListNarrowedByType') {
+ return (
+
+ );
+ }
+
+ if (currentPanelView === 'relatedEventDetail') {
+ const parentCount: number = Object.values(
+ relatedStatsForIdFromParams?.events.byCategory || {}
+ ).reduce((sum, val) => sum + val, 0);
+ return (
+
+ );
+ }
+ // The default 'Event List' / 'List of all processes' view
+ return ;
+ }, [
+ uiSelectedEvent,
+ crumbEvent,
+ crumbId,
+ pushToQueryParams,
+ relatedStatsForIdFromParams,
+ currentPanelView,
+ ]);
+
+ return <>{panelInstance}>;
+});
+PanelContent.displayName = 'PanelContent';
+
+export const Panel = memo(function Event({ className }: { className?: string }) {
return (
-
-
- {i18n.translate('xpack.securitySolution.endpoint.resolver.panel.title', {
- defaultMessage: 'Processes',
- })}
-
-
-
- items={processTableView} columns={columns} sorting />
+
);
});
+Panel.displayName = 'Panel';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
new file mode 100644
index 000000000000..c9a536fd5932
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
+import React, { memo, useMemo } from 'react';
+import { CrumbInfo, StyledBreadcrumbs } from './panel_content_utilities';
+
+/**
+ * Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state.
+ *
+ * @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
+ * @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
+ */
+export const PanelContentError = memo(function ({
+ translatedErrorMessage,
+ pushToQueryParams,
+}: {
+ translatedErrorMessage: string;
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+}) {
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
+ defaultMessage: 'Events',
+ }),
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
+ defaultMessage: 'Error',
+ }),
+ onClick: () => {},
+ },
+ ];
+ }, [pushToQueryParams]);
+ return (
+ <>
+
+
+ {translatedErrorMessage}
+
+ {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ }}
+ >
+ {i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
+ defaultMessage: 'Click this link to return to the list of all processes.',
+ })}
+
+ >
+ );
+});
+PanelContentError.displayName = 'TableServiceError';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
new file mode 100644
index 000000000000..fcb7bf1d12e1
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
@@ -0,0 +1,211 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ htmlIdGenerator,
+ EuiSpacer,
+ EuiTitle,
+ EuiText,
+ EuiTextColor,
+ EuiDescriptionList,
+} from '@elastic/eui';
+import styled from 'styled-components';
+import { FormattedMessage } from 'react-intl';
+import * as event from '../../../../common/endpoint/models/event';
+import { CrumbInfo, formatDate, StyledBreadcrumbs } from './panel_content_utilities';
+import {
+ processPath,
+ processPid,
+ userInfoForProcess,
+ processParentPid,
+ md5HashForProcess,
+ argsForProcess,
+} from '../../models/process_event';
+import { CubeForProcess } from './process_cube_icon';
+import { ResolverEvent } from '../../../../common/endpoint/types';
+import { useResolverTheme } from '../assets';
+
+const StyledDescriptionList = styled(EuiDescriptionList)`
+ &.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
+ max-width: 8em;
+ }
+`;
+
+/**
+ * A description list view of all the Metadata that goes with a particular process event, like:
+ * Created, Pid, User/Domain, etc.
+ */
+export const ProcessDetails = memo(function ProcessDetails({
+ processEvent,
+ pushToQueryParams,
+}: {
+ processEvent: ResolverEvent;
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+}) {
+ const processName = event.eventName(processEvent);
+ const processInfoEntry = useMemo(() => {
+ const eventTime = event.eventTimestamp(processEvent);
+ const dateTime = eventTime ? formatDate(eventTime) : '';
+
+ const createdEntry = {
+ title: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.created',
+ {
+ defaultMessage: 'Created',
+ }
+ ),
+ description: dateTime,
+ };
+
+ const pathEntry = {
+ title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.path', {
+ defaultMessage: 'Path',
+ }),
+ description: processPath(processEvent),
+ };
+
+ const pidEntry = {
+ title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.pid', {
+ defaultMessage: 'PID',
+ }),
+ description: processPid(processEvent),
+ };
+
+ const userEntry = {
+ title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.user', {
+ defaultMessage: 'User',
+ }),
+ description: (userInfoForProcess(processEvent) as { name: string }).name,
+ };
+
+ const domainEntry = {
+ title: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.domain',
+ {
+ defaultMessage: 'Domain',
+ }
+ ),
+ description: (userInfoForProcess(processEvent) as { domain: string }).domain,
+ };
+
+ const parentPidEntry = {
+ title: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.parentPid',
+ {
+ defaultMessage: 'Parent PID',
+ }
+ ),
+ description: processParentPid(processEvent),
+ };
+
+ const md5Entry = {
+ title: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.md5hash',
+ {
+ defaultMessage: 'MD5',
+ }
+ ),
+ description: md5HashForProcess(processEvent),
+ };
+
+ const commandLineEntry = {
+ title: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.commandLine',
+ {
+ defaultMessage: 'Command Line',
+ }
+ ),
+ description: argsForProcess(processEvent),
+ };
+
+ // This is the data in {title, description} form for the EUIDescriptionList to display
+ const processDescriptionListData = [
+ createdEntry,
+ pathEntry,
+ pidEntry,
+ userEntry,
+ domainEntry,
+ parentPidEntry,
+ md5Entry,
+ commandLineEntry,
+ ]
+ .filter((entry) => {
+ return entry.description;
+ })
+ .map((entry) => {
+ return {
+ ...entry,
+ description: String(entry.description),
+ };
+ });
+
+ return processDescriptionListData;
+ }, [processEvent]);
+
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processDescList.events',
+ {
+ defaultMessage: 'Events',
+ }
+ ),
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {},
+ },
+ ];
+ }, [processName, pushToQueryParams]);
+ const { cubeAssetsForNode } = useResolverTheme();
+ const { descriptionText } = useMemo(() => {
+ if (!processEvent) {
+ return { descriptionText: '' };
+ }
+ return cubeAssetsForNode(processEvent);
+ }, [processEvent, cubeAssetsForNode]);
+
+ const titleId = useMemo(() => htmlIdGenerator('resolverTable')(), []);
+ return (
+ <>
+
+
+
+
+
+ {processName}
+
+
+
+
+ {descriptionText}
+
+
+
+
+ >
+ );
+});
+ProcessDetails.displayName = 'ProcessDetails';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx
new file mode 100644
index 000000000000..86ae10b3b38c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx
@@ -0,0 +1,164 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useContext, useCallback, useMemo } from 'react';
+import {
+ EuiBasicTableColumn,
+ EuiBadge,
+ EuiButtonEmpty,
+ EuiSpacer,
+ EuiInMemoryTable,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { useSelector } from 'react-redux';
+import * as event from '../../../../common/endpoint/models/event';
+import * as selectors from '../../store/selectors';
+import { CrumbInfo, formatter, StyledBreadcrumbs } from './panel_content_utilities';
+import { useResolverDispatch } from '../use_resolver_dispatch';
+import { SideEffectContext } from '../side_effect_context';
+import { CubeForProcess } from './process_cube_icon';
+import { ResolverEvent } from '../../../../common/endpoint/types';
+
+/**
+ * The "default" view for the panel: A list of all the processes currently in the graph.
+ *
+ * @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
+ */
+export const ProcessListWithCounts = memo(function ProcessListWithCounts({
+ pushToQueryParams,
+}: {
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+}) {
+ interface ProcessTableView {
+ name: string;
+ timestamp?: Date;
+ event: ResolverEvent;
+ }
+
+ const dispatch = useResolverDispatch();
+ const { timestamp } = useContext(SideEffectContext);
+ const handleBringIntoViewClick = useCallback(
+ (processTableViewItem) => {
+ dispatch({
+ type: 'userBroughtProcessIntoView',
+ payload: {
+ time: timestamp(),
+ process: processTableViewItem.event,
+ },
+ });
+ pushToQueryParams({ crumbId: event.entityId(processTableViewItem.event), crumbEvent: '' });
+ },
+ [dispatch, timestamp, pushToQueryParams]
+ );
+
+ const columns = useMemo>>(
+ () => [
+ {
+ field: 'name',
+ name: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.table.row.processNameTitle',
+ {
+ defaultMessage: 'Process Name',
+ }
+ ),
+ sortable: true,
+ truncateText: true,
+ render(name: string, item: ProcessTableView) {
+ return name === '' ? (
+
+ {i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.table.row.valueMissingDescription',
+ {
+ defaultMessage: 'Value is missing',
+ }
+ )}
+
+ ) : (
+ {
+ handleBringIntoViewClick(item);
+ pushToQueryParams({ crumbId: event.entityId(item.event), crumbEvent: '' });
+ }}
+ >
+
+ {name}
+
+ );
+ },
+ },
+ {
+ field: 'timestamp',
+ name: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.table.row.timestampTitle',
+ {
+ defaultMessage: 'Timestamp',
+ }
+ ),
+ dataType: 'date',
+ sortable: true,
+ render(eventDate?: Date) {
+ return eventDate ? (
+ formatter.format(eventDate)
+ ) : (
+
+ {i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.table.row.timestampInvalidLabel',
+ {
+ defaultMessage: 'invalid',
+ }
+ )}
+
+ );
+ },
+ },
+ ],
+ [pushToQueryParams, handleBringIntoViewClick]
+ );
+
+ const { processNodePositions } = useSelector(selectors.processNodePositionsAndEdgeLineSegments);
+ const processTableView: ProcessTableView[] = useMemo(
+ () =>
+ [...processNodePositions.keys()].map((processEvent) => {
+ let dateTime;
+ const eventTime = event.eventTimestamp(processEvent);
+ const name = event.eventName(processEvent);
+ if (eventTime) {
+ const date = new Date(eventTime);
+ if (isFinite(date.getTime())) {
+ dateTime = date;
+ }
+ }
+ return {
+ name,
+ timestamp: dateTime,
+ event: processEvent,
+ };
+ }),
+ [processNodePositions]
+ );
+
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processListWithCounts.events',
+ {
+ defaultMessage: 'All Process Events',
+ }
+ ),
+ onClick: () => {},
+ },
+ ];
+ }, []);
+
+ return (
+ <>
+
+
+ items={processTableView} columns={columns} sorting />
+ >
+ );
+});
+ProcessListWithCounts.displayName = 'ProcessListWithCounts';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_counts.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_counts.tsx
new file mode 100644
index 000000000000..2e4211f568ff
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_counts.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiBasicTableColumn, EuiButtonEmpty, EuiSpacer, EuiInMemoryTable } from '@elastic/eui';
+import { FormattedMessage } from 'react-intl';
+import { CrumbInfo, StyledBreadcrumbs } from './panel_content_utilities';
+
+import * as event from '../../../../common/endpoint/models/event';
+import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
+
+/**
+ * This view gives counts for all the related events of a process grouped by related event type.
+ * It should look something like:
+ *
+ * | Count | Event Type |
+ * | :--------------------- | :------------------------- |
+ * | 5 | DNS |
+ * | 12 | Registry |
+ * | 2 | Network |
+ *
+ */
+export const EventCountsForProcess = memo(function EventCountsForProcess({
+ processEvent,
+ pushToQueryParams,
+ relatedStats,
+}: {
+ processEvent: ResolverEvent;
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+ relatedStats: ResolverNodeStats;
+}) {
+ interface EventCountsTableView {
+ name: string;
+ count: number;
+ }
+
+ const relatedEventsState = { stats: relatedStats.events.byCategory };
+ const processName = processEvent && event.eventName(processEvent);
+ const processEntityId = event.entityId(processEvent);
+ /**
+ * totalCount: This will reflect the aggregated total by category for all related events
+ * e.g. [dns,file],[dns,file],[registry] will have an aggregate total of 5. This is to keep the
+ * total number consistent with the "broken out" totals we see elsewhere in the app.
+ * E.g. on the rleated list by type, the above would show as:
+ * 2 dns
+ * 2 file
+ * 1 registry
+ * So it would be extremely disorienting to show the user a "3" above that as a total.
+ */
+ const totalCount = Object.values(relatedStats.events.byCategory).reduce(
+ (sum, val) => sum + val,
+ 0
+ );
+ const eventsString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processEventCounts.events',
+ {
+ defaultMessage: 'Events',
+ }
+ );
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: eventsString,
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: processName,
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId, crumbEvent: '' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId, crumbEvent: '' });
+ },
+ },
+ ];
+ }, [processName, totalCount, processEntityId, pushToQueryParams, eventsString]);
+ const rows = useMemo(() => {
+ return Object.entries(relatedEventsState.stats).map(
+ ([eventType, count]): EventCountsTableView => {
+ return {
+ name: eventType,
+ count,
+ };
+ }
+ );
+ }, [relatedEventsState]);
+ const columns = useMemo>>(
+ () => [
+ {
+ field: 'count',
+ name: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.table.row.count', {
+ defaultMessage: 'Count',
+ }),
+ width: '20%',
+ sortable: true,
+ },
+ {
+ field: 'name',
+ name: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.table.row.eventType', {
+ defaultMessage: 'Event Type',
+ }),
+ width: '80%',
+ sortable: true,
+ render(name: string) {
+ return (
+ {
+ pushToQueryParams({ crumbId: event.entityId(processEvent), crumbEvent: name });
+ }}
+ >
+ {name}
+
+ );
+ },
+ },
+ ],
+ [pushToQueryParams, processEvent]
+ );
+ return (
+ <>
+
+
+ items={rows} columns={columns} sorting />
+ >
+ );
+});
+EventCountsForProcess.displayName = 'EventCountsForProcess';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
new file mode 100644
index 000000000000..1fe6599e0829
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
@@ -0,0 +1,365 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo, useMemo, useEffect, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '@elastic/eui';
+import styled from 'styled-components';
+import { useSelector } from 'react-redux';
+import { FormattedMessage } from 'react-intl';
+import { CrumbInfo, formatDate, StyledBreadcrumbs, BoldCode } from './panel_content_utilities';
+import * as event from '../../../../common/endpoint/models/event';
+import { ResolverEvent } from '../../../../common/endpoint/types';
+import * as selectors from '../../store/selectors';
+import { useResolverDispatch } from '../use_resolver_dispatch';
+import { PanelContentError } from './panel_content_error';
+
+/**
+ * A helper function to turn objects into EuiDescriptionList entries.
+ * This reflects the strategy of more or less "dumping" metadata for related processes
+ * in description lists with little/no 'prettification'. This has the obvious drawback of
+ * data perhaps appearing inscrutable/daunting, but the benefit of presenting these fields
+ * to the user "as they occur" in ECS, which may help them with e.g. EQL queries.
+ *
+ * Given an object like: {a:{b: 1}, c: 'd'} it will yield title/description entries like so:
+ * {title: "a.b", description: "1"}, {title: "c", description: "d"}
+ *
+ * @param {object} obj The object to turn into `` entries
+ */
+const objectToDescriptionListEntries = function* (
+ obj: object,
+ prefix = ''
+): Generator<{ title: string; description: string }> {
+ const nextPrefix = prefix.length ? `${prefix}.` : '';
+ for (const [metaKey, metaValue] of Object.entries(obj)) {
+ if (typeof metaValue === 'number' || typeof metaValue === 'string') {
+ yield { title: nextPrefix + metaKey, description: `${metaValue}` };
+ } else if (metaValue instanceof Array) {
+ yield {
+ title: nextPrefix + metaKey,
+ description: metaValue
+ .filter((arrayEntry) => {
+ return typeof arrayEntry === 'number' || typeof arrayEntry === 'string';
+ })
+ .join(','),
+ };
+ } else if (typeof metaValue === 'object') {
+ yield* objectToDescriptionListEntries(metaValue, nextPrefix + metaKey);
+ }
+ }
+};
+
+// Adding some styles to prevent horizontal scrollbars, per request from UX review
+const StyledDescriptionList = memo(styled(EuiDescriptionList)`
+ &.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
+ max-width: 8em;
+ }
+ &.euiDescriptionList.euiDescriptionList--column dd.euiDescriptionList__description {
+ max-width: calc(100% - 8.5em);
+ overflow-wrap: break-word;
+ }
+`);
+
+// Styling subtitles, per UX review:
+const StyledFlexTitle = memo(styled('h3')`
+ display: flex;
+ flex-flow: row;
+ font-size: 1.2em;
+`);
+const StyledTitleRule = memo(styled('hr')`
+ &.euiHorizontalRule.euiHorizontalRule--full.euiHorizontalRule--marginSmall.override {
+ display: block;
+ flex: 1;
+ margin-left: 0.5em;
+ }
+`);
+
+const TitleHr = memo(() => {
+ return (
+
+ );
+});
+TitleHr.displayName = 'TitleHR';
+
+/**
+ * This view presents a detailed view of all the available data for a related event, split and titled by the "section"
+ * it appears in the underlying ResolverEvent
+ */
+export const RelatedEventDetail = memo(function RelatedEventDetail({
+ relatedEventId,
+ parentEvent,
+ pushToQueryParams,
+ countForParent,
+}: {
+ relatedEventId: string;
+ parentEvent: ResolverEvent;
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+ countForParent: number | undefined;
+}) {
+ const processName = (parentEvent && event.eventName(parentEvent)) || '*';
+ const processEntityId = parentEvent && event.entityId(parentEvent);
+ const totalCount = countForParent || 0;
+ const eventsString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events',
+ {
+ defaultMessage: 'Events',
+ }
+ );
+ const naString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.NA',
+ {
+ defaultMessage: 'N/A',
+ }
+ );
+
+ const relatedsReadyMap = useSelector(selectors.relatedEventsReady);
+ const relatedsReady = relatedsReadyMap.get(processEntityId!);
+ const dispatch = useResolverDispatch();
+
+ /**
+ * If we don't have the related events for the parent yet, use this effect
+ * to request them.
+ */
+ useEffect(() => {
+ if (typeof relatedsReady === 'undefined') {
+ dispatch({
+ type: 'appDetectedMissingEventData',
+ payload: processEntityId,
+ });
+ }
+ }, [relatedsReady, dispatch, processEntityId]);
+
+ const relatedEventsForThisProcess = useSelector(selectors.relatedEventsByEntityId).get(
+ processEntityId!
+ );
+
+ const [relatedEventToShowDetailsFor, countBySameCategory, relatedEventCategory] = useMemo(() => {
+ if (!relatedEventsForThisProcess) {
+ return [undefined, 0];
+ }
+ const specificEvent = relatedEventsForThisProcess.events.find(
+ (evt) => event.eventId(evt) === relatedEventId
+ );
+ // For breadcrumbs:
+ const specificCategory = specificEvent && event.primaryEventCategory(specificEvent);
+ const countOfCategory = relatedEventsForThisProcess.events.reduce((sumtotal, evt) => {
+ return event.primaryEventCategory(evt) === specificCategory ? sumtotal + 1 : sumtotal;
+ }, 0);
+ return [specificEvent, countOfCategory, specificCategory || naString];
+ }, [relatedEventsForThisProcess, naString, relatedEventId]);
+
+ const [sections, formattedDate] = useMemo(() => {
+ if (!relatedEventToShowDetailsFor) {
+ // This could happen if user relaods from URL param and requests an eventId that no longer exists
+ return [[], naString];
+ }
+ // Assuming these details (agent, ecs, process) aren't as helpful, can revisit
+ const {
+ agent,
+ ecs,
+ process,
+ ...relevantData
+ } = relatedEventToShowDetailsFor as ResolverEvent & {
+ ecs: unknown;
+ };
+ let displayDate = '';
+ const sectionData: Array<{
+ sectionTitle: string;
+ entries: Array<{ title: string; description: string }>;
+ }> = Object.entries(relevantData)
+ .map(([sectionTitle, val]) => {
+ if (sectionTitle === '@timestamp') {
+ displayDate = formatDate(val);
+ return { sectionTitle: '', entries: [] };
+ }
+ if (typeof val !== 'object') {
+ return { sectionTitle, entries: [{ title: sectionTitle, description: `${val}` }] };
+ }
+ return { sectionTitle, entries: [...objectToDescriptionListEntries(val)] };
+ })
+ .filter((v) => v.sectionTitle !== '' && v.entries.length);
+ return [sectionData, displayDate];
+ }, [relatedEventToShowDetailsFor, naString]);
+
+ const waitCrumbs = useMemo(() => {
+ return [
+ {
+ text: eventsString,
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ ];
+ }, [pushToQueryParams, eventsString]);
+
+ const { subject = '', descriptor = '' } = relatedEventToShowDetailsFor
+ ? event.descriptiveName(relatedEventToShowDetailsFor)
+ : {};
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: eventsString,
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: processName,
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId!, crumbEvent: '' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId!, crumbEvent: 'all' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {
+ pushToQueryParams({
+ crumbId: processEntityId!,
+ crumbEvent: relatedEventCategory || 'all',
+ });
+ },
+ },
+ {
+ text: relatedEventToShowDetailsFor ? (
+
+ ) : (
+ naString
+ ),
+ onClick: () => {},
+ },
+ ];
+ }, [
+ processName,
+ processEntityId,
+ eventsString,
+ pushToQueryParams,
+ totalCount,
+ countBySameCategory,
+ naString,
+ relatedEventCategory,
+ relatedEventToShowDetailsFor,
+ subject,
+ descriptor,
+ ]);
+
+ /**
+ * If the ship hasn't come in yet, wait on the dock
+ */
+ if (!relatedsReady) {
+ const waitingString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.relatedDetail.wait',
+ {
+ defaultMessage: 'Waiting For Events...',
+ }
+ );
+ return (
+ <>
+
+
+
+ {waitingString}
+
+ >
+ );
+ }
+
+ /**
+ * Could happen if user e.g. loads a URL with a bad crumbEvent
+ */
+ if (!relatedEventToShowDetailsFor) {
+ const errString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.relatedDetail.missing',
+ {
+ defaultMessage: 'Related event not found.',
+ }
+ );
+ return (
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {sections.map(({ sectionTitle, entries }, index) => {
+ return (
+
+ {index === 0 ? null : }
+
+
+
+ {sectionTitle}
+
+
+
+
+
+ {index === sections.length - 1 ? null : }
+
+ );
+ })}
+ >
+ );
+});
+RelatedEventDetail.displayName = 'RelatedEventDetail';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_list.tsx
new file mode 100644
index 000000000000..c9c303010d10
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_list.tsx
@@ -0,0 +1,247 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo, useMemo, useEffect, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiTitle, EuiSpacer, EuiText, EuiButtonEmpty, EuiHorizontalRule } from '@elastic/eui';
+import { useSelector } from 'react-redux';
+import { FormattedMessage } from 'react-intl';
+import { CrumbInfo, formatDate, StyledBreadcrumbs, BoldCode } from './panel_content_utilities';
+import * as event from '../../../../common/endpoint/models/event';
+import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
+import * as selectors from '../../store/selectors';
+import { useResolverDispatch } from '../use_resolver_dispatch';
+
+/**
+ * This view presents a list of related events of a given type for a given process.
+ * It will appear like:
+ *
+ * | |
+ * | :----------------------------------------------------- |
+ * | **registry deletion** @ *3:32PM..* *HKLM/software...* |
+ * | **file creation** @ *3:34PM..* *C:/directory/file.exe* |
+ */
+
+interface MatchingEventEntry {
+ formattedDate: string;
+ eventType: string;
+ eventCategory: string;
+ name: { subject: string; descriptor?: string };
+ entityId: string;
+ setQueryParams: () => void;
+}
+
+const DisplayList = memo(function DisplayList({
+ crumbs,
+ matchingEventEntries,
+}: {
+ crumbs: Array<{ text: string | JSX.Element; onClick: () => void }>;
+ matchingEventEntries: MatchingEventEntry[];
+}) {
+ return (
+ <>
+
+
+ <>
+ {matchingEventEntries.map((eventView, index) => {
+ const { subject, descriptor = '' } = eventView.name;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {index === matchingEventEntries.length - 1 ? null : }
+
+ );
+ })}
+ >
+ >
+ );
+});
+
+export const ProcessEventListNarrowedByType = memo(function ProcessEventListNarrowedByType({
+ processEvent,
+ eventType,
+ relatedStats,
+ pushToQueryParams,
+}: {
+ processEvent: ResolverEvent;
+ pushToQueryParams: (arg0: CrumbInfo) => unknown;
+ eventType: string;
+ relatedStats: ResolverNodeStats;
+}) {
+ const processName = processEvent && event.eventName(processEvent);
+ const processEntityId = event.entityId(processEvent);
+ const totalCount = Object.values(relatedStats.events.byCategory).reduce(
+ (sum, val) => sum + val,
+ 0
+ );
+ const eventsString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processEventListByType.events',
+ {
+ defaultMessage: 'Events',
+ }
+ );
+ const waitingString = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.panel.processEventListByType.wait',
+ {
+ defaultMessage: 'Waiting For Events...',
+ }
+ );
+
+ const relatedsReadyMap = useSelector(selectors.relatedEventsReady);
+ const relatedsReady = relatedsReadyMap.get(processEntityId);
+
+ const relatedEventsForThisProcess = useSelector(selectors.relatedEventsByEntityId).get(
+ processEntityId
+ );
+ const dispatch = useResolverDispatch();
+
+ useEffect(() => {
+ if (typeof relatedsReady === 'undefined') {
+ dispatch({
+ type: 'appDetectedMissingEventData',
+ payload: processEntityId,
+ });
+ }
+ }, [relatedsReady, dispatch, processEntityId]);
+
+ const waitCrumbs = useMemo(() => {
+ return [
+ {
+ text: eventsString,
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ ];
+ }, [pushToQueryParams, eventsString]);
+
+ const relatedEventsToDisplay = useMemo(() => {
+ return relatedEventsForThisProcess?.events || [];
+ }, [relatedEventsForThisProcess?.events]);
+
+ /**
+ * A list entry will be displayed for each of these
+ */
+ const matchingEventEntries: MatchingEventEntry[] = useMemo(() => {
+ const relateds = relatedEventsToDisplay
+ .reduce((a: ResolverEvent[], candidate) => {
+ if (event.primaryEventCategory(candidate) === eventType) {
+ a.push(candidate);
+ }
+ return a;
+ }, [])
+ .map((resolverEvent) => {
+ const eventTime = event.eventTimestamp(resolverEvent);
+ const formattedDate = typeof eventTime === 'undefined' ? '' : formatDate(eventTime);
+ const entityId = event.eventId(resolverEvent);
+
+ return {
+ formattedDate,
+ eventCategory: `${eventType}`,
+ eventType: `${event.ecsEventType(resolverEvent)}`,
+ name: event.descriptiveName(resolverEvent),
+ entityId,
+ setQueryParams: () => {
+ pushToQueryParams({ crumbId: entityId, crumbEvent: processEntityId });
+ },
+ };
+ });
+ return relateds;
+ }, [relatedEventsToDisplay, eventType, processEntityId, pushToQueryParams]);
+
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: eventsString,
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: processName,
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId, crumbEvent: '' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {
+ pushToQueryParams({ crumbId: processEntityId, crumbEvent: 'all' });
+ },
+ },
+ {
+ text: (
+ <>
+
+ >
+ ),
+ onClick: () => {},
+ },
+ ];
+ }, [
+ eventType,
+ eventsString,
+ matchingEventEntries.length,
+ processEntityId,
+ processName,
+ pushToQueryParams,
+ totalCount,
+ ]);
+
+ /**
+ * Wait here until the effect resolves...
+ */
+ if (!relatedsReady) {
+ return (
+ <>
+
+
+
+ {waitingString}
+
+ >
+ );
+ }
+
+ return ;
+});
+ProcessEventListNarrowedByType.displayName = 'ProcessEventListNarrowedByType';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
new file mode 100644
index 000000000000..65422d3d705d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
@@ -0,0 +1,90 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { EuiBreadcrumbs, Breadcrumb, EuiCode } from '@elastic/eui';
+import styled from 'styled-components';
+import React, { memo } from 'react';
+import { useResolverTheme } from '../assets';
+
+/**
+ * A bold version of EuiCode to display certain titles with
+ */
+export const BoldCode = styled(EuiCode)`
+ &.euiCodeBlock code.euiCodeBlock__code {
+ font-weight: 900;
+ }
+`;
+
+/**
+ * The two query parameters we read/write on to control which view the table presents:
+ */
+export interface CrumbInfo {
+ readonly crumbId: string;
+ readonly crumbEvent: string;
+}
+
+const ThemedBreadcrumbs = styled(EuiBreadcrumbs)<{ background: string; text: string }>`
+ &.euiBreadcrumbs.euiBreadcrumbs--responsive {
+ background-color: ${(props) => props.background};
+ color: ${(props) => props.text};
+ padding: 1em;
+ }
+`;
+
+/**
+ * Breadcrumb menu with adjustments per direction from UX team
+ */
+export const StyledBreadcrumbs = memo(function StyledBreadcrumbs({
+ breadcrumbs,
+ truncate,
+}: {
+ breadcrumbs: Breadcrumb[];
+ truncate?: boolean;
+}) {
+ const {
+ colorMap: { resolverEdge, resolverEdgeText },
+ } = useResolverTheme();
+ return (
+
+ );
+});
+
+/**
+ * Long formatter (to second) for DateTime
+ */
+export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+});
+
+const invalidDateText = i18n.translate(
+ 'xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate',
+ {
+ defaultMessage: 'Invalid Date',
+ }
+);
+/**
+ * @param {ConstructorParameters[0]} timestamp To be passed through Date->Intl.DateTimeFormat
+ * @returns {string} A nicely formatted string for a date
+ */
+export function formatDate(timestamp: ConstructorParameters[0]) {
+ const date = new Date(timestamp);
+ if (isFinite(date.getTime())) {
+ return formatter.format(date);
+ } else {
+ return invalidDateText;
+ }
+}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx
new file mode 100644
index 000000000000..29ffe154d571
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo } from 'react';
+import { ResolverEvent } from '../../../../common/endpoint/types';
+import { useResolverTheme } from '../assets';
+
+/**
+ * During user testing, one user indicated they wanted to see stronger visual relationships between
+ * Nodes on the graph and what's in the table. Using the same symbol in both places (as below) could help with that.
+ */
+export const CubeForProcess = memo(function CubeForProcess({
+ processEvent,
+}: {
+ processEvent: ResolverEvent;
+}) {
+ const { cubeAssetsForNode } = useResolverTheme();
+ const { cubeSymbol, descriptionText } = cubeAssetsForNode(processEvent);
+
+ return (
+ <>
+
+ {descriptionText}
+
+
+ >
+ );
+});
diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
index 7b463f0bed26..78b70611a697 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
@@ -7,195 +7,185 @@
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
-import {
- htmlIdGenerator,
- EuiButton,
- EuiI18nNumber,
- EuiKeyboardAccessible,
- EuiFlexGroup,
- EuiFlexItem,
-} from '@elastic/eui';
+import { htmlIdGenerator, EuiButton, EuiI18nNumber, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+// eslint-disable-next-line import/no-nodejs-modules
+import querystring from 'querystring';
import { NodeSubMenu, subMenuAssets } from './submenu';
import { applyMatrix3 } from '../lib/vector2';
-import { Vector2, Matrix3, AdjacentProcessMap, ResolverProcessType } from '../types';
-import { SymbolIds, useResolverTheme, NodeStyleMap, calculateResolverFontSize } from './assets';
+import { Vector2, Matrix3, AdjacentProcessMap } from '../types';
+import { SymbolIds, useResolverTheme, calculateResolverFontSize, nodeType } from './assets';
import { ResolverEvent, ResolverNodeStats } from '../../../common/endpoint/types';
import { useResolverDispatch } from './use_resolver_dispatch';
import * as eventModel from '../../../common/endpoint/models/event';
-import * as processModel from '../models/process_event';
import * as selectors from '../store/selectors';
+import { CrumbInfo } from './panels/panel_content_utilities';
+
+/**
+ * A map of all known event types (in ugly schema format) to beautifully i18n'd display names
+ */
+export const displayNameRecord = {
+ application: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.applicationEventTypeDisplayName',
+ {
+ defaultMessage: 'Application',
+ }
+ ),
+ apm: i18n.translate('xpack.securitySolution.endpoint.resolver.apmEventTypeDisplayName', {
+ defaultMessage: 'APM',
+ }),
+ audit: i18n.translate('xpack.securitySolution.endpoint.resolver.auditEventTypeDisplayName', {
+ defaultMessage: 'Audit',
+ }),
+ authentication: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.authenticationEventTypeDisplayName',
+ {
+ defaultMessage: 'Authentication',
+ }
+ ),
+ certificate: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.certificateEventTypeDisplayName',
+ {
+ defaultMessage: 'Certificate',
+ }
+ ),
+ cloud: i18n.translate('xpack.securitySolution.endpoint.resolver.cloudEventTypeDisplayName', {
+ defaultMessage: 'Cloud',
+ }),
+ database: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.databaseEventTypeDisplayName',
+ {
+ defaultMessage: 'Database',
+ }
+ ),
+ driver: i18n.translate('xpack.securitySolution.endpoint.resolver.driverEventTypeDisplayName', {
+ defaultMessage: 'Driver',
+ }),
+ email: i18n.translate('xpack.securitySolution.endpoint.resolver.emailEventTypeDisplayName', {
+ defaultMessage: 'Email',
+ }),
+ file: i18n.translate('xpack.securitySolution.endpoint.resolver.fileEventTypeDisplayName', {
+ defaultMessage: 'File',
+ }),
+ host: i18n.translate('xpack.securitySolution.endpoint.resolver.hostEventTypeDisplayName', {
+ defaultMessage: 'Host',
+ }),
+ iam: i18n.translate('xpack.securitySolution.endpoint.resolver.iamEventTypeDisplayName', {
+ defaultMessage: 'IAM',
+ }),
+ iam_group: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.iam_groupEventTypeDisplayName',
+ {
+ defaultMessage: 'IAM Group',
+ }
+ ),
+ intrusion_detection: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.intrusion_detectionEventTypeDisplayName',
+ {
+ defaultMessage: 'Intrusion Detection',
+ }
+ ),
+ malware: i18n.translate('xpack.securitySolution.endpoint.resolver.malwareEventTypeDisplayName', {
+ defaultMessage: 'Malware',
+ }),
+ network_flow: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.network_flowEventTypeDisplayName',
+ {
+ defaultMessage: 'Network Flow',
+ }
+ ),
+ network: i18n.translate('xpack.securitySolution.endpoint.resolver.networkEventTypeDisplayName', {
+ defaultMessage: 'Network',
+ }),
+ package: i18n.translate('xpack.securitySolution.endpoint.resolver.packageEventTypeDisplayName', {
+ defaultMessage: 'Package',
+ }),
+ process: i18n.translate('xpack.securitySolution.endpoint.resolver.processEventTypeDisplayName', {
+ defaultMessage: 'Process',
+ }),
+ registry: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.registryEventTypeDisplayName',
+ {
+ defaultMessage: 'Registry',
+ }
+ ),
+ session: i18n.translate('xpack.securitySolution.endpoint.resolver.sessionEventTypeDisplayName', {
+ defaultMessage: 'Session',
+ }),
+ service: i18n.translate('xpack.securitySolution.endpoint.resolver.serviceEventTypeDisplayName', {
+ defaultMessage: 'Service',
+ }),
+ socket: i18n.translate('xpack.securitySolution.endpoint.resolver.socketEventTypeDisplayName', {
+ defaultMessage: 'Socket',
+ }),
+ vulnerability: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.vulnerabilityEventTypeDisplayName',
+ {
+ defaultMessage: 'Vulnerability',
+ }
+ ),
+ web: i18n.translate('xpack.securitySolution.endpoint.resolver.webEventTypeDisplayName', {
+ defaultMessage: 'Web',
+ }),
+ alert: i18n.translate('xpack.securitySolution.endpoint.resolver.alertEventTypeDisplayName', {
+ defaultMessage: 'Alert',
+ }),
+ security: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.securityEventTypeDisplayName',
+ {
+ defaultMessage: 'Security',
+ }
+ ),
+ dns: i18n.translate('xpack.securitySolution.endpoint.resolver.dnsEventTypeDisplayName', {
+ defaultMessage: 'DNS',
+ }),
+ clr: i18n.translate('xpack.securitySolution.endpoint.resolver.clrEventTypeDisplayName', {
+ defaultMessage: 'CLR',
+ }),
+ image_load: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.image_loadEventTypeDisplayName',
+ {
+ defaultMessage: 'Image Load',
+ }
+ ),
+ powershell: i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.powershellEventTypeDisplayName',
+ {
+ defaultMessage: 'Powershell',
+ }
+ ),
+ wmi: i18n.translate('xpack.securitySolution.endpoint.resolver.wmiEventTypeDisplayName', {
+ defaultMessage: 'WMI',
+ }),
+ api: i18n.translate('xpack.securitySolution.endpoint.resolver.apiEventTypeDisplayName', {
+ defaultMessage: 'API',
+ }),
+ user: i18n.translate('xpack.securitySolution.endpoint.resolver.userEventTypeDisplayName', {
+ defaultMessage: 'User',
+ }),
+} as const;
+
+const unknownEventTypeMessage = i18n.translate(
+ 'xpack.securitySolution.endpoint.resolver.userEventTypeDisplayUnknown',
+ {
+ defaultMessage: 'Unknown',
+ }
+);
+
+type EventDisplayName = typeof displayNameRecord[keyof typeof displayNameRecord] &
+ typeof unknownEventTypeMessage;
/**
* Take a gross `schemaName` and return a beautiful translated one.
*/
-const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName(
- schemaName: string
+const getDisplayName: (schemaName: string) => EventDisplayName = function nameInSchemaToDisplayName(
+ schemaName
) {
- const displayNameRecord: Record = {
- application: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.applicationEventTypeDisplayName',
- {
- defaultMessage: 'Application',
- }
- ),
- apm: i18n.translate('xpack.securitySolution.endpoint.resolver.apmEventTypeDisplayName', {
- defaultMessage: 'APM',
- }),
- audit: i18n.translate('xpack.securitySolution.endpoint.resolver.auditEventTypeDisplayName', {
- defaultMessage: 'Audit',
- }),
- authentication: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.authenticationEventTypeDisplayName',
- {
- defaultMessage: 'Authentication',
- }
- ),
- certificate: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.certificateEventTypeDisplayName',
- {
- defaultMessage: 'Certificate',
- }
- ),
- cloud: i18n.translate('xpack.securitySolution.endpoint.resolver.cloudEventTypeDisplayName', {
- defaultMessage: 'Cloud',
- }),
- database: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.databaseEventTypeDisplayName',
- {
- defaultMessage: 'Database',
- }
- ),
- driver: i18n.translate('xpack.securitySolution.endpoint.resolver.driverEventTypeDisplayName', {
- defaultMessage: 'Driver',
- }),
- email: i18n.translate('xpack.securitySolution.endpoint.resolver.emailEventTypeDisplayName', {
- defaultMessage: 'Email',
- }),
- file: i18n.translate('xpack.securitySolution.endpoint.resolver.fileEventTypeDisplayName', {
- defaultMessage: 'File',
- }),
- host: i18n.translate('xpack.securitySolution.endpoint.resolver.hostEventTypeDisplayName', {
- defaultMessage: 'Host',
- }),
- iam: i18n.translate('xpack.securitySolution.endpoint.resolver.iamEventTypeDisplayName', {
- defaultMessage: 'IAM',
- }),
- iam_group: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.iam_groupEventTypeDisplayName',
- {
- defaultMessage: 'IAM Group',
- }
- ),
- intrusion_detection: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.intrusion_detectionEventTypeDisplayName',
- {
- defaultMessage: 'Intrusion Detection',
- }
- ),
- malware: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.malwareEventTypeDisplayName',
- {
- defaultMessage: 'Malware',
- }
- ),
- network_flow: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.network_flowEventTypeDisplayName',
- {
- defaultMessage: 'Network Flow',
- }
- ),
- network: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.networkEventTypeDisplayName',
- {
- defaultMessage: 'Network',
- }
- ),
- package: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.packageEventTypeDisplayName',
- {
- defaultMessage: 'Package',
- }
- ),
- process: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.processEventTypeDisplayName',
- {
- defaultMessage: 'Process',
- }
- ),
- registry: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.registryEventTypeDisplayName',
- {
- defaultMessage: 'Registry',
- }
- ),
- session: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.sessionEventTypeDisplayName',
- {
- defaultMessage: 'Session',
- }
- ),
- service: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.serviceEventTypeDisplayName',
- {
- defaultMessage: 'Service',
- }
- ),
- socket: i18n.translate('xpack.securitySolution.endpoint.resolver.socketEventTypeDisplayName', {
- defaultMessage: 'Socket',
- }),
- vulnerability: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.vulnerabilityEventTypeDisplayName',
- {
- defaultMessage: 'Vulnerability',
- }
- ),
- web: i18n.translate('xpack.securitySolution.endpoint.resolver.webEventTypeDisplayName', {
- defaultMessage: 'Web',
- }),
- alert: i18n.translate('xpack.securitySolution.endpoint.resolver.alertEventTypeDisplayName', {
- defaultMessage: 'Alert',
- }),
- security: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.securityEventTypeDisplayName',
- {
- defaultMessage: 'Security',
- }
- ),
- dns: i18n.translate('xpack.securitySolution.endpoint.resolver.dnsEventTypeDisplayName', {
- defaultMessage: 'DNS',
- }),
- clr: i18n.translate('xpack.securitySolution.endpoint.resolver.clrEventTypeDisplayName', {
- defaultMessage: 'CLR',
- }),
- image_load: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.image_loadEventTypeDisplayName',
- {
- defaultMessage: 'Image Load',
- }
- ),
- powershell: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.powershellEventTypeDisplayName',
- {
- defaultMessage: 'Powershell',
- }
- ),
- wmi: i18n.translate('xpack.securitySolution.endpoint.resolver.wmiEventTypeDisplayName', {
- defaultMessage: 'WMI',
- }),
- api: i18n.translate('xpack.securitySolution.endpoint.resolver.apiEventTypeDisplayName', {
- defaultMessage: 'API',
- }),
- user: i18n.translate('xpack.securitySolution.endpoint.resolver.userEventTypeDisplayName', {
- defaultMessage: 'User',
- }),
- };
- return (
- displayNameRecord[schemaName] ||
- i18n.translate('xpack.securitySolution.endpoint.resolver.userEventTypeDisplayUnknown', {
- defaultMessage: 'Unknown',
- })
- );
+ if (schemaName in displayNameRecord) {
+ return displayNameRecord[schemaName as keyof typeof displayNameRecord];
+ }
+ return unknownEventTypeMessage;
};
interface StyledActionsContainer {
@@ -283,11 +273,14 @@ const ProcessEventDotComponents = React.memo(
const [magFactorX] = projectionMatrix;
+ // Node (html id=) IDs
const selfId = adjacentNodeMap.self;
-
const activeDescendantId = useSelector(selectors.uiActiveDescendantId);
const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId);
+ // Entity ID of self
+ const selfEntityId = eventModel.entityId(event);
+
const isShowingEventActions = magFactorX > 0.8;
const isShowingDescriptionText = magFactorX >= 0.55;
@@ -401,6 +394,50 @@ const ProcessEventDotComponents = React.memo(
});
}, [dispatch, nodeId]);
+ const handleRelatedEventRequest = useCallback(() => {
+ dispatch({
+ type: 'userRequestedRelatedEventData',
+ payload: selfId,
+ });
+ }, [dispatch, selfId]);
+
+ const handleRelatedAlertsRequest = useCallback(() => {
+ dispatch({
+ type: 'userSelectedRelatedAlerts',
+ payload: event,
+ });
+ }, [dispatch, event]);
+
+ const history = useHistory();
+ const urlSearch = history.location.search;
+
+ /**
+ * This updates the breadcrumb nav, the table view
+ */
+ const pushToQueryParams = useCallback(
+ (newCrumbs: CrumbInfo) => {
+ // Construct a new set of params from the current set (minus empty params)
+ // by assigning the new set of params provided in `newCrumbs`
+ const crumbsToPass = {
+ ...querystring.parse(urlSearch.slice(1)),
+ ...newCrumbs,
+ };
+
+ // If either was passed in as empty, remove it from the record
+ if (crumbsToPass.crumbId === '') {
+ delete crumbsToPass.crumbId;
+ }
+ if (crumbsToPass.crumbEvent === '') {
+ delete crumbsToPass.crumbEvent;
+ }
+
+ const relativeURL = { search: querystring.stringify(crumbsToPass) };
+
+ return history.replace(relativeURL);
+ },
+ [history, urlSearch]
+ );
+
const handleClick = useCallback(() => {
if (animationTarget.current !== null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -410,39 +447,31 @@ const ProcessEventDotComponents = React.memo(
type: 'userSelectedResolverNode',
payload: {
nodeId,
+ selectedProcessId: selfId,
},
});
- }, [animationTarget, dispatch, nodeId]);
+ pushToQueryParams({ crumbId: selfEntityId, crumbEvent: 'all' });
+ }, [animationTarget, dispatch, nodeId, selfEntityId, pushToQueryParams, selfId]);
- const handleRelatedEventRequest = useCallback(() => {
- dispatch({
- type: 'userRequestedRelatedEventData',
- payload: event,
- });
- }, [dispatch, event]);
-
- const handleRelatedAlertsRequest = useCallback(() => {
- dispatch({
- type: 'userSelectedRelatedAlerts',
- payload: event,
- });
- }, [dispatch, event]);
/**
* Enumerates the stats for related events to display with the node as options,
* generally in the form `number of related events in category` `category title`
* e.g. "10 DNS", "230 File"
*/
- const relatedEventOptions = useMemo(() => {
+
+ const [relatedEventOptions, grandTotal] = useMemo(() => {
const relatedStatsList = [];
if (!relatedEventsStats) {
// Return an empty set of options if there are no stats to report
- return [];
+ return [[], 0];
}
+ let runningTotal = 0;
// If we have entries to show, map them into options to display in the selectable list
for (const category in relatedEventsStats.events.byCategory) {
if (Object.hasOwnProperty.call(relatedEventsStats.events.byCategory, category)) {
const total = relatedEventsStats.events.byCategory[category];
+ runningTotal += total;
const displayName = getDisplayName(category);
relatedStatsList.push({
prefix: ,
@@ -455,12 +484,14 @@ const ProcessEventDotComponents = React.memo(
category,
},
});
+
+ pushToQueryParams({ crumbId: selfEntityId, crumbEvent: category });
},
});
}
}
- return relatedStatsList;
- }, [relatedEventsStats, dispatch, event]);
+ return [relatedStatsList, runningTotal];
+ }, [relatedEventsStats, dispatch, event, pushToQueryParams, selfEntityId]);
const relatedEventStatusOrOptions = (() => {
if (!relatedEventsStats) {
@@ -475,144 +506,144 @@ const ProcessEventDotComponents = React.memo(
* Key event handling (e.g. 'Enter'/'Space') is provisioned by the `EuiKeyboardAccessible` component
*/
return (
-
-
+
+
+
+
+
+
+
+
+
-
+ {descriptionText}
+
+ = 2 ? 'euiButton' : 'euiButton euiButton--small'}
+ data-test-subject="nodeLabel"
+ id={labelId}
+ onClick={handleClick}
+ onFocus={handleFocus}
+ tabIndex={-1}
style={{
- display: 'block',
- width: '100%',
- height: '100%',
- position: 'absolute',
- top: '0',
- left: '0',
+ backgroundColor: colorMap.resolverBackground,
+ alignSelf: 'flex-start',
+ padding: 0,
}}
>
-
-
-
-
-
-
-
-
-
- {descriptionText}
-
-
-
-
-
- {eventModel.eventName(event)}
-
+
+
+ {eventModel.eventName(event)}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
/* eslint-enable jsx-a11y/click-events-have-key-events */
}
@@ -673,21 +704,3 @@ export const ProcessEventDot = styled(ProcessEventDotComponents)`
color: white;
}
`;
-
-const processTypeToCube: Record = {
- processCreated: 'runningProcessCube',
- processRan: 'runningProcessCube',
- processTerminated: 'terminatedProcessCube',
- unknownProcessEvent: 'runningProcessCube',
- processCausedAlert: 'runningTriggerCube',
- unknownEvent: 'runningProcessCube',
-};
-
-function nodeType(processEvent: ResolverEvent): keyof NodeStyleMap {
- const processType = processModel.eventType(processEvent);
-
- if (processType in processTypeToCube) {
- return processTypeToCube[processType];
- }
- return 'runningProcessCube';
-}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx b/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx
index 861c170b8b0b..8f972dd737af 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx
@@ -6,7 +6,14 @@
import { i18n } from '@kbn/i18n';
import React, { ReactNode, useState, useMemo, useCallback } from 'react';
-import { EuiSelectable, EuiButton, EuiPopover, ButtonColor, htmlIdGenerator } from '@elastic/eui';
+import {
+ EuiI18nNumber,
+ EuiSelectable,
+ EuiButton,
+ EuiPopover,
+ ButtonColor,
+ htmlIdGenerator,
+} from '@elastic/eui';
import styled from 'styled-components';
/**
@@ -74,21 +81,44 @@ const OptionList = React.memo(
};
})
);
- return useMemo(
- () => (
- {
- setOptions(newOptions);
- }}
- listProps={{ showIcons: true, bordered: true }}
- isLoading={isLoading}
- >
- {(list) => {list} }
-
- ),
- [isLoading, options]
+
+ const actionsByLabel: Record unknown> = useMemo(() => {
+ if (typeof subMenuOptions !== 'object') {
+ return {};
+ }
+ return subMenuOptions.reduce((titleActionRecord, opt) => {
+ const { optionTitle, action } = opt;
+ return { ...titleActionRecord, [optionTitle]: action };
+ }, {});
+ }, [subMenuOptions]);
+
+ type ChangeOptions = Array<{ label: string; prepend?: ReactNode; checked?: string }>;
+ const selectableProps = useMemo(() => {
+ return {
+ listProps: { showIcons: true, bordered: true },
+ onChange: (newOptions: ChangeOptions) => {
+ const selectedOption = newOptions.find((opt) => opt.checked === 'on');
+ if (selectedOption) {
+ const { label } = selectedOption;
+ const actionToTake = actionsByLabel[label];
+ if (typeof actionToTake === 'function') {
+ actionToTake();
+ }
+ }
+ setOptions(newOptions);
+ },
+ };
+ }, [actionsByLabel]);
+
+ return (
+
+ {(list) => {list} }
+
);
}
);
@@ -102,6 +132,7 @@ OptionList.displayName = 'OptionList';
*/
const NodeSubMenuComponents = React.memo(
({
+ count,
buttonBorderColor,
menuTitle,
menuAction,
@@ -113,6 +144,7 @@ const NodeSubMenuComponents = React.memo(
menuAction?: () => unknown;
buttonBorderColor: ButtonColor;
buttonFill: string;
+ count?: number;
} & {
optionsWithActions?: ResolverSubmenuOptionList | string | undefined;
}) => {
@@ -176,7 +208,7 @@ const NodeSubMenuComponents = React.memo(
iconSide="right"
tabIndex={-1}
>
- {menuTitle}
+ {count ? : ''} {menuTitle}
);
From e467096fdd21d32c226abdc3a6427aad4d9d60e8 Mon Sep 17 00:00:00 2001
From: Marshall Main <55718608+marshallmain@users.noreply.github.com>
Date: Thu, 18 Jun 2020 20:41:22 -0400
Subject: [PATCH 12/33] Remove endpoint alert fields from signal mapping
(#68934) (#69598)
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../routes/index/ecs_mapping.json | 1705 ++---------------
1 file changed, 187 insertions(+), 1518 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json
index 6a404e390bd1..6126ee462ec2 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json
@@ -7,14 +7,6 @@
},
"agent": {
"properties": {
- "build": {
- "properties": {
- "original": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"ephemeral_id": {
"ignore_above": 1024,
"type": "keyword"
@@ -37,6 +29,27 @@
}
}
},
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
"client": {
"properties": {
"address": {
@@ -202,10 +215,6 @@
"id": {
"ignore_above": 1024,
"type": "keyword"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
}
}
},
@@ -233,18 +242,6 @@
}
}
},
- "project": {
- "properties": {
- "id": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"provider": {
"ignore_above": 1024,
"type": "keyword"
@@ -255,6 +252,27 @@
}
}
},
+ "code_signature": {
+ "properties": {
+ "exists": {
+ "type": "boolean"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
"container": {
"properties": {
"id": {
@@ -467,9 +485,6 @@
}
}
},
- "compile_time": {
- "type": "date"
- },
"hash": {
"properties": {
"md5": {
@@ -490,53 +505,6 @@
}
}
},
- "malware_classification": {
- "properties": {
- "features": {
- "properties": {
- "data": {
- "properties": {
- "buffer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "decompressed_size": {
- "type": "integer"
- },
- "encoding": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "identifier": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "score": {
- "type": "double"
- },
- "threshold": {
- "type": "double"
- },
- "upx_packed": {
- "type": "boolean"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "mapped_address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "mapped_size": {
- "type": "long"
- },
"name": {
"ignore_above": 1024,
"type": "keyword"
@@ -547,10 +515,6 @@
},
"pe": {
"properties": {
- "architecture": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"company": {
"ignore_above": 1024,
"type": "keyword"
@@ -563,10 +527,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "imphash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"original_file_name": {
"ignore_above": 1024,
"type": "keyword"
@@ -666,49 +626,6 @@
}
}
},
- "endpoint": {
- "properties": {
- "artifact": {
- "properties": {
- "hash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "event": {
- "properties": {
- "process": {
- "properties": {
- "ancestry": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "object"
- }
- },
- "type": "object"
- },
- "policy": {
- "properties": {
- "id": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "object"
- }
- }
- },
"error": {
"properties": {
"code": {
@@ -882,9 +799,6 @@
"ignore_above": 1,
"type": "keyword"
},
- "entry_modified": {
- "type": "double"
- },
"extension": {
"ignore_above": 1024,
"type": "keyword"
@@ -921,156 +835,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "macro": {
- "properties": {
- "code_page": {
- "type": "long"
- },
- "collection": {
- "properties": {
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- },
- "type": "object"
- },
- "errors": {
- "properties": {
- "count": {
- "type": "long"
- },
- "error_type": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "file_extension": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "project_file": {
- "properties": {
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- },
- "type": "object"
- },
- "stream": {
- "properties": {
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "raw_code": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "raw_code_size": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- }
- }
- },
- "malware_classification": {
- "properties": {
- "features": {
- "properties": {
- "data": {
- "properties": {
- "buffer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "decompressed_size": {
- "type": "integer"
- },
- "encoding": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "identifier": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "score": {
- "type": "double"
- },
- "threshold": {
- "type": "double"
- },
- "upx_packed": {
- "type": "boolean"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"mime_type": {
"ignore_above": 1024,
"type": "keyword"
@@ -1102,10 +866,6 @@
},
"pe": {
"properties": {
- "architecture": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"company": {
"ignore_above": 1024,
"type": "keyword"
@@ -1118,10 +878,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "imphash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"original_file_name": {
"ignore_above": 1024,
"type": "keyword"
@@ -1132,13 +888,6 @@
}
}
},
- "quarantine_path": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "quarantine_result": {
- "type": "boolean"
- },
"size": {
"type": "long"
},
@@ -1152,123 +901,48 @@
"ignore_above": 1024,
"type": "keyword"
},
- "temp_file_path": {
+ "type": {
"ignore_above": 1024,
"type": "keyword"
},
- "type": {
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
"ignore_above": 1024,
"type": "keyword"
},
- "uid": {
+ "continent_name": {
"ignore_above": 1024,
"type": "keyword"
},
- "x509": {
- "properties": {
- "alternative_names": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "issuer": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "not_after": {
- "type": "date"
- },
- "not_before": {
- "type": "date"
- },
- "public_key_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_curve": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_exponent": {
- "doc_values": false,
- "index": false,
- "type": "long"
- },
- "public_key_size": {
- "type": "long"
- },
- "serial_number": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "signature_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "version_number": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
}
}
},
@@ -1288,6 +962,26 @@
}
}
},
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
"host": {
"properties": {
"architecture": {
@@ -1386,10 +1080,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "variant": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"version": {
"ignore_above": 1024,
"type": "keyword"
@@ -1519,10 +1209,6 @@
},
"status_code": {
"type": "long"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
}
}
},
@@ -1532,19 +1218,27 @@
}
}
},
+ "interface": {
+ "properties": {
+ "alias": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
"labels": {
"type": "object"
},
"log": {
"properties": {
- "file": {
- "properties": {
- "path": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"level": {
"ignore_above": 1024,
"type": "keyword"
@@ -1848,10 +1542,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "variant": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"version": {
"ignore_above": 1024,
"type": "keyword"
@@ -1898,6 +1588,46 @@
}
}
},
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
"package": {
"properties": {
"architecture": {
@@ -1952,6 +1682,30 @@
}
}
},
+ "pe": {
+ "properties": {
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
"process": {
"properties": {
"args": {
@@ -2029,46 +1783,6 @@
}
}
},
- "malware_classification": {
- "properties": {
- "features": {
- "properties": {
- "data": {
- "properties": {
- "buffer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "decompressed_size": {
- "type": "integer"
- },
- "encoding": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "identifier": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "score": {
- "type": "double"
- },
- "threshold": {
- "type": "double"
- },
- "upx_packed": {
- "type": "boolean"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"name": {
"fields": {
"text": {
@@ -2216,10 +1930,6 @@
},
"pe": {
"properties": {
- "architecture": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"company": {
"ignore_above": 1024,
"type": "keyword"
@@ -2232,10 +1942,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "imphash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"original_file_name": {
"ignore_above": 1024,
"type": "keyword"
@@ -2255,132 +1961,17 @@
"ppid": {
"type": "long"
},
- "services": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"start": {
"type": "date"
},
"thread": {
"properties": {
- "call_stack": {
- "properties": {
- "instruction_pointer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "memory_section": {
- "properties": {
- "address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "protection": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "size": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "module_path": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "rva": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "symbol_info": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"id": {
"type": "long"
},
"name": {
"ignore_above": 1024,
"type": "keyword"
- },
- "service": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "start": {
- "type": "date"
- },
- "start_address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "start_address_module": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "token": {
- "properties": {
- "domain": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "elevation": {
- "type": "boolean"
- },
- "elevation_type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "impersonation_level": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "integrity_level": {
- "type": "long"
- },
- "integrity_level_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "is_appcontainer": {
- "type": "boolean"
- },
- "privileges": {
- "properties": {
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "enabled": {
- "type": "boolean"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sid": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "uptime": {
- "type": "long"
}
}
},
@@ -2394,70 +1985,9 @@
"ignore_above": 1024,
"type": "keyword"
},
- "token": {
- "properties": {
- "domain": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "elevation": {
- "type": "boolean"
- },
- "elevation_type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "impersonation_level": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "integrity_level": {
- "type": "long"
- },
- "integrity_level_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "is_appcontainer": {
- "type": "boolean"
- },
- "privileges": {
- "properties": {
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "enabled": {
- "type": "boolean"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sid": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
"uptime": {
"type": "long"
},
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"working_directory": {
"fields": {
"text": {
@@ -2921,663 +2451,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "target": {
- "properties": {
- "dll": {
- "properties": {
- "code_signature": {
- "properties": {
- "exists": {
- "type": "boolean"
- },
- "status": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "trusted": {
- "type": "boolean"
- },
- "valid": {
- "type": "boolean"
- }
- }
- },
- "compile_time": {
- "type": "date"
- },
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "malware_classification": {
- "properties": {
- "features": {
- "properties": {
- "data": {
- "properties": {
- "buffer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "decompressed_size": {
- "type": "integer"
- },
- "encoding": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "identifier": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "score": {
- "type": "double"
- },
- "threshold": {
- "type": "double"
- },
- "upx_packed": {
- "type": "boolean"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "mapped_address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "mapped_size": {
- "type": "long"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "path": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "pe": {
- "properties": {
- "architecture": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "company": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "file_version": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "imphash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "original_file_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "product": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "process": {
- "properties": {
- "args": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "args_count": {
- "type": "long"
- },
- "code_signature": {
- "properties": {
- "exists": {
- "type": "boolean"
- },
- "status": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "trusted": {
- "type": "boolean"
- },
- "valid": {
- "type": "boolean"
- }
- }
- },
- "command_line": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "entity_id": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "executable": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "exit_code": {
- "type": "long"
- },
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "malware_classification": {
- "properties": {
- "features": {
- "properties": {
- "data": {
- "properties": {
- "buffer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "decompressed_size": {
- "type": "integer"
- },
- "encoding": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
- "identifier": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "score": {
- "type": "double"
- },
- "threshold": {
- "type": "double"
- },
- "upx_packed": {
- "type": "boolean"
- },
- "version": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "name": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "parent": {
- "properties": {
- "args": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "args_count": {
- "type": "long"
- },
- "code_signature": {
- "properties": {
- "exists": {
- "type": "boolean"
- },
- "status": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "trusted": {
- "type": "boolean"
- },
- "valid": {
- "type": "boolean"
- }
- }
- },
- "command_line": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "entity_id": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "executable": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "exit_code": {
- "type": "long"
- },
- "hash": {
- "properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "name": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "pgid": {
- "type": "long"
- },
- "pid": {
- "type": "long"
- },
- "ppid": {
- "type": "long"
- },
- "start": {
- "type": "date"
- },
- "thread": {
- "properties": {
- "id": {
- "type": "long"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "title": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "uptime": {
- "type": "long"
- },
- "working_directory": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "pe": {
- "properties": {
- "architecture": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "company": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "file_version": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "imphash": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "original_file_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "product": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "pgid": {
- "type": "long"
- },
- "pid": {
- "type": "long"
- },
- "ppid": {
- "type": "long"
- },
- "services": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "start": {
- "type": "date"
- },
- "thread": {
- "properties": {
- "call_stack": {
- "properties": {
- "instruction_pointer": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "memory_section": {
- "properties": {
- "address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "protection": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "size": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "module_path": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "rva": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "symbol_info": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "id": {
- "type": "long"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "service": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "start": {
- "type": "date"
- },
- "start_address": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "start_address_module": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "token": {
- "properties": {
- "domain": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "elevation": {
- "type": "boolean"
- },
- "elevation_type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "impersonation_level": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "integrity_level": {
- "type": "long"
- },
- "integrity_level_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "is_appcontainer": {
- "type": "boolean"
- },
- "privileges": {
- "properties": {
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "enabled": {
- "type": "boolean"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sid": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "uptime": {
- "type": "long"
- }
- }
- },
- "title": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- },
- "token": {
- "properties": {
- "domain": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "elevation": {
- "type": "boolean"
- },
- "elevation_type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "impersonation_level": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "integrity_level": {
- "type": "long"
- },
- "integrity_level_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "is_appcontainer": {
- "type": "boolean"
- },
- "privileges": {
- "properties": {
- "description": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "enabled": {
- "type": "boolean"
- },
- "name": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sid": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "type": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "uptime": {
- "type": "long"
- },
- "user": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "working_directory": {
- "fields": {
- "text": {
- "norms": false,
- "type": "text"
- }
- },
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- }
- }
- },
"threat": {
"properties": {
"framework": {
@@ -3681,112 +2554,6 @@
"supported_ciphers": {
"ignore_above": 1024,
"type": "keyword"
- },
- "x509": {
- "properties": {
- "alternative_names": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "issuer": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "not_after": {
- "type": "date"
- },
- "not_before": {
- "type": "date"
- },
- "public_key_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_curve": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_exponent": {
- "doc_values": false,
- "index": false,
- "type": "long"
- },
- "public_key_size": {
- "type": "long"
- },
- "serial_number": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "signature_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "version_number": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
}
}
},
@@ -3847,112 +2614,6 @@
"subject": {
"ignore_above": 1024,
"type": "keyword"
- },
- "x509": {
- "properties": {
- "alternative_names": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "issuer": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "not_after": {
- "type": "date"
- },
- "not_before": {
- "type": "date"
- },
- "public_key_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_curve": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "public_key_exponent": {
- "doc_values": false,
- "index": false,
- "type": "long"
- },
- "public_key_size": {
- "type": "long"
- },
- "serial_number": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "signature_algorithm": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "subject": {
- "properties": {
- "common_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "country": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "distinguished_name": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "locality": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organization": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "organizational_unit": {
- "ignore_above": 1024,
- "type": "keyword"
- },
- "state_or_province": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
- },
- "version_number": {
- "ignore_above": 1024,
- "type": "keyword"
- }
- }
}
}
},
@@ -4163,10 +2824,6 @@
"ignore_above": 1024,
"type": "keyword"
},
- "variant": {
- "ignore_above": 1024,
- "type": "keyword"
- },
"version": {
"ignore_above": 1024,
"type": "keyword"
@@ -4179,6 +2836,18 @@
}
}
},
+ "vlan": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
"vulnerability": {
"properties": {
"category": {
From fe79a6117ebb15efd50219dbf1e816b2362efca0 Mon Sep 17 00:00:00 2001
From: Sandra Gonzales
Date: Thu, 18 Jun 2020 22:12:34 -0400
Subject: [PATCH 13/33] don't include group fields with no child fields in
index pattern (#69457) (#69556)
---
.../index_pattern/__snapshots__/install.test.ts.snap | 2 +-
.../epm/kibana/index_pattern/install.test.ts | 12 +++++++++---
.../services/epm/kibana/index_pattern/install.ts | 4 ++++
.../epm/kibana/index_pattern/tests/nginx.fields.yml | 2 ++
4 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap
index 029e278b5aa9..d8495840453f 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap
+++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap
@@ -611,7 +611,7 @@ exports[`creating index patterns from yaml fields createIndexPatternFields funct
}
`;
-exports[`creating index patterns from yaml fields flattenFields function flattens recursively and handles copying alias fields: flattenFields 1`] = `
+exports[`creating index patterns from yaml fields flattenFields function flattens recursively and handles copying alias fields flattenFields matches snapshot: flattenFields 1`] = `
[
{
"name": "coredns.id",
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts
index d083b40e40fd..3425a698bd17 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts
@@ -56,9 +56,15 @@ describe('creating index patterns from yaml fields', () => {
expect(indexPattern).toMatchSnapshot('createIndexPattern');
});
- test('flattenFields function flattens recursively and handles copying alias fields', () => {
- const flattened = flattenFields(fields);
- expect(flattened).toMatchSnapshot('flattenFields');
+ describe('flattenFields function flattens recursively and handles copying alias fields', () => {
+ test('a field of type group with no nested fields is skipped', () => {
+ const flattened = flattenFields([{ name: 'nginx', type: 'group' }]);
+ expect(flattened.length).toBe(0);
+ });
+ test('flattenFields matches snapshot', () => {
+ const flattened = flattenFields(fields);
+ expect(flattened).toMatchSnapshot('flattenFields');
+ });
});
describe('dedupFields', () => {
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts
index 0f7b1d6cab17..69cd35f3050c 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts
@@ -307,6 +307,10 @@ export const transformField = (field: Field, i: number, fields: Fields): IndexPa
export const flattenFields = (allFields: Fields): Fields => {
const flatten = (fields: Fields): Fields =>
fields.reduce((acc, field) => {
+ // if this is a group fields with no fields, skip the field
+ if (field.type === 'group' && !field.fields?.length) {
+ return acc;
+ }
// recurse through nested fields
if (field.type === 'group' && field.fields?.length) {
// skip if field.enabled is not explicitly set to false
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml
index 220225a2c246..7c2e721d564e 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml
+++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml
@@ -116,3 +116,5 @@
type: keyword
- name: text
type: text
+- name: nginx
+ type: group
\ No newline at end of file
From 19ca9848c882b2ae771ba27b10829c22003300f0 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Thu, 18 Jun 2020 23:01:08 -0400
Subject: [PATCH 14/33] Resolving conflicts (#69597) (#69604)
---
.../apis/endpoint/alerts/index.ts | 176 ------------------
1 file changed, 176 deletions(-)
diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
index ed287834761d..45f6a1515d04 100644
--- a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
+++ b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts
@@ -5,8 +5,6 @@
*/
import expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { AlertData } from '../../../../../plugins/security_solution/common/endpoint_alerts/types';
-import { eventsIndexPattern } from '../../../../../plugins/security_solution/common/endpoint/constants';
import {
deleteEventsStream,
deleteMetadataStream,
@@ -26,46 +24,8 @@ const numberOfAlertsInFixture = numberOfHosts * numberOfAlertsPerHost;
*/
const defaultPageSize = 10;
-/**
- * `NULLABLE_EVENT_FIELD` should be a field in the fixture that exists for some alerts,
- * but not all.
- *
- * This allows us to test sorting and paging on mixed data that may or may not exist
- * for each alert.
- */
-const NULLABLE_EVENT_FIELD = 'process.parent.entity_id';
-
-/**
- * An Elasticsearch query to get the alert (or alerts) without `NULLABLE_EVENT_FIELD`.
- */
-const ES_QUERY_MISSING = {
- query: {
- bool: {
- must: [
- {
- bool: {
- must_not: {
- exists: {
- field: NULLABLE_EVENT_FIELD,
- },
- },
- },
- },
- {
- term: {
- 'event.kind': {
- value: 'alert',
- },
- },
- },
- ],
- },
- },
-};
-
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
- const es = getService('legacyEs');
const client = getService('es');
const nextPrevPrefixQuery = "query=(language:kuery,query:'')";
const nextPrevPrefixDateRange = "date_range=(from:'2018-01-10T00:00:00.000Z',to:now)";
@@ -74,8 +34,6 @@ export default function ({ getService }: FtrProviderContext) {
const nextPrevPrefixPageSize = 'page_size=10';
const nextPrevPrefix = `${nextPrevPrefixQuery}&${nextPrevPrefixDateRange}&${nextPrevPrefixSort}&${nextPrevPrefixOrder}&${nextPrevPrefixPageSize}`;
- let nullableEventId = '';
-
describe('Endpoint alert API', () => {
describe('when data is in elasticsearch', () => {
before(async () => {
@@ -89,12 +47,6 @@ export default function ({ getService }: FtrProviderContext) {
'events-endpoint-1',
numberOfAlertsPerHost
);
-
- const res = await es.search({
- index: eventsIndexPattern,
- body: ES_QUERY_MISSING,
- });
- nullableEventId = res.hits.hits[0]._source.event.id;
});
after(async () => {
@@ -260,134 +212,6 @@ export default function ({ getService }: FtrProviderContext) {
expect(emptyBody.alerts.length).to.eql(0);
});
- it('alerts api should return data using `before` by custom sort parameter, descending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&${nextPrevPrefixOrder}&sort=process.name&before=malware%20writer&before=4d7afd81-26ec-47c0-9741-ae16d331f73d`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
- let valid: boolean = true;
- (body.alerts as AlertData[]).forEach((alert) => {
- if (alert.process?.name > 'malware writer') {
- valid = false;
- }
- });
- expect(valid).to.eql(true);
- });
-
- it('alerts api should return data using `before` on undefined primary sort values by custom sort parameter, descending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&order=desc&sort=${NULLABLE_EVENT_FIELD}&before=&before=${nullableEventId}&empty_string_is_undefined=true`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
-
- let lastSeen: string | undefined = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz';
- let valid: boolean = true;
-
- for (const alert of body.alerts) {
- const entityId = alert.process?.parent?.entity_id;
- if (entityId === undefined && alert.event.id > nullableEventId) {
- valid = false;
- }
- if (entityId !== undefined && lastSeen !== undefined && entityId > lastSeen) {
- valid = false;
- } else {
- lastSeen = entityId;
- }
- }
-
- expect(valid).to.eql(true);
- });
-
- it('alerts api should return data using `before` on undefined primary sort values by custom sort parameter, ascending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&page_size=25&order=asc&sort=${NULLABLE_EVENT_FIELD}&before=&before=${nullableEventId}&empty_string_is_undefined=true`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
-
- let lastSeen: string | undefined = '1';
- let valid: boolean = true;
-
- for (const alert of body.alerts) {
- const entityId = alert.process?.parent?.entity_id;
- if (entityId === undefined && alert.event.id < nullableEventId) {
- valid = false;
- }
- if (entityId !== undefined && lastSeen !== undefined && entityId < lastSeen) {
- valid = false;
- } else {
- lastSeen = entityId;
- }
- }
- expect(valid).to.eql(true);
- });
-
- it('should return data using `after` by custom sort parameter, descending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&${nextPrevPrefixOrder}&sort=process.pid&after=3&after=66008e21-2493-4b15-a937-939ea228064a`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
- expect(body.alerts.length).to.eql(10);
- expect(body.alerts[0].process.pid).to.eql(2);
- });
-
- it('alerts api should return data using `after` on undefined primary sort values by custom sort parameter, descending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&sort=${NULLABLE_EVENT_FIELD}&order=desc&after=&after=${nullableEventId}&empty_string_is_undefined=true`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
-
- let lastSeen: string | undefined = 'zzzzzzzzzzzzzzzzzzzzzzzzzzz';
- let valid: boolean = true;
-
- for (const alert of body.alerts) {
- const entityId = alert.process?.parent?.entity_id;
- if (entityId === undefined && alert.event.id < nullableEventId) {
- valid = false;
- }
- if (entityId !== undefined && lastSeen !== undefined && entityId > lastSeen) {
- valid = false;
- } else {
- lastSeen = entityId;
- }
- }
- expect(valid).to.eql(true);
- });
-
- it('alerts api should return data using `after` on undefined primary sort values by custom sort parameter, ascending', async () => {
- const { body } = await supertest
- .get(
- `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&sort=${NULLABLE_EVENT_FIELD}&order=asc&after=&after=${nullableEventId}&empty_string_is_undefined=true`
- )
- .set('kbn-xsrf', 'xxx')
- .expect(200);
-
- let lastSeen: string | undefined = '1';
- let valid: boolean = true;
-
- for (const alert of body.alerts) {
- const entityId = alert.process?.parent?.entity_id;
- if (entityId === undefined && alert.event.id < nullableEventId) {
- valid = false;
- }
- if (entityId !== undefined && lastSeen !== undefined && entityId < lastSeen) {
- valid = false;
- } else {
- lastSeen = entityId;
- }
- }
- expect(valid).to.eql(true);
- });
-
it('should filter results of alert data using rison-encoded filters', async () => {
const { body: firstBody } = await supertest
.get('/api/endpoint/alerts?page_index=0')
From d38eb1dc498165a20f39a2f93574883b640f5cc5 Mon Sep 17 00:00:00 2001
From: patrykkopycinski
Date: Fri, 19 Jun 2020 08:43:25 +0200
Subject: [PATCH 15/33] [Security Solution] Add cypress tests for global search
bar (#68535) (#69557)
Co-authored-by: Elastic Machine
---
.../cypress/integration/search_bar.spec.ts | 26 +++++++++++++++
.../cypress/objects/filter.ts | 15 +++++++++
.../cypress/screens/search_bar.ts | 32 ++++++++++++++++++
.../cypress/tasks/search_bar.ts | 33 +++++++++++++++++++
4 files changed, 106 insertions(+)
create mode 100644 x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
create mode 100644 x-pack/plugins/security_solution/cypress/objects/filter.ts
create mode 100644 x-pack/plugins/security_solution/cypress/screens/search_bar.ts
create mode 100644 x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
new file mode 100644
index 000000000000..103d4d45b3a0
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loginAndWaitForPage } from '../tasks/login';
+import { openAddFilterPopover, fillAddFilterForm } from '../tasks/search_bar';
+import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../screens/search_bar';
+import { hostIpFilter } from '../objects/filter';
+
+import { HOSTS_PAGE } from '../urls/navigation';
+import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts';
+
+describe('SearchBar', () => {
+ before(() => {
+ loginAndWaitForPage(HOSTS_PAGE);
+ waitForAllHostsToBeLoaded();
+ });
+
+ it('adds correctly a filter to the global search bar', () => {
+ openAddFilterPopover();
+ fillAddFilterForm(hostIpFilter);
+ cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM(hostIpFilter)).should('be.visible');
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/filter.ts b/x-pack/plugins/security_solution/cypress/objects/filter.ts
new file mode 100644
index 000000000000..7d9fa5c9e7ad
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/objects/filter.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export interface SearchBarFilter {
+ key: string;
+ value: string;
+}
+
+export const hostIpFilter: SearchBarFilter = {
+ key: 'host.ip',
+ value: '1.1.1.1',
+};
diff --git a/x-pack/plugins/security_solution/cypress/screens/search_bar.ts b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts
new file mode 100644
index 000000000000..35864749a406
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SearchBarFilter } from '../objects/filter';
+
+export const GLOBAL_SEARCH_BAR_ADD_FILTER =
+ '[data-test-subj="globalDatePicker"] [data-test-subj="addFilter"]';
+
+export const GLOBAL_SEARCH_BAR_SUBMIT_BUTTON =
+ '[data-test-subj="globalDatePicker"] [data-test-subj="querySubmitButton"]';
+
+export const ADD_FILTER_FORM_FIELD_INPUT =
+ '[data-test-subj="filterFieldSuggestionList"] input[data-test-subj="comboBoxSearchInput"]';
+
+export const ADD_FILTER_FORM_FIELD_OPTION = (value: string) =>
+ `[data-test-subj="comboBoxOptionsList filterFieldSuggestionList-optionsList"] button[title="${value}"] strong`;
+
+export const ADD_FILTER_FORM_OPERATOR_FIELD =
+ '[data-test-subj="filterOperatorList"] input[data-test-subj="comboBoxSearchInput"]';
+
+export const ADD_FILTER_FORM_OPERATOR_OPTION_IS =
+ '[data-test-subj="comboBoxOptionsList filterOperatorList-optionsList"] button[title="is"]';
+
+export const ADD_FILTER_FORM_FILTER_VALUE_INPUT = '[data-test-subj="filterParams"] input';
+
+export const ADD_FILTER_FORM_SAVE_BUTTON = '[data-test-subj="saveFilter"]';
+
+export const GLOBAL_SEARCH_BAR_FILTER_ITEM = ({ key, value }: SearchBarFilter) =>
+ `[data-test-subj="filter filter-enabled filter-key-${key} filter-value-${value} filter-unpinned"]`;
diff --git a/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts b/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
new file mode 100644
index 000000000000..01d712460b44
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SearchBarFilter } from '../objects/filter';
+
+import {
+ GLOBAL_SEARCH_BAR_ADD_FILTER,
+ GLOBAL_SEARCH_BAR_SUBMIT_BUTTON,
+ ADD_FILTER_FORM_SAVE_BUTTON,
+ ADD_FILTER_FORM_FIELD_INPUT,
+ ADD_FILTER_FORM_OPERATOR_OPTION_IS,
+ ADD_FILTER_FORM_OPERATOR_FIELD,
+ ADD_FILTER_FORM_FIELD_OPTION,
+ ADD_FILTER_FORM_FILTER_VALUE_INPUT,
+} from '../screens/search_bar';
+
+export const openAddFilterPopover = () => {
+ cy.get(GLOBAL_SEARCH_BAR_SUBMIT_BUTTON).should('be.enabled');
+ cy.get(GLOBAL_SEARCH_BAR_ADD_FILTER).click({ force: true });
+};
+
+export const fillAddFilterForm = ({ key, value }: SearchBarFilter) => {
+ cy.get(ADD_FILTER_FORM_FIELD_INPUT).type(key);
+ cy.get(ADD_FILTER_FORM_FIELD_INPUT).click();
+ cy.get(ADD_FILTER_FORM_FIELD_OPTION(key)).click({ force: true });
+ cy.get(ADD_FILTER_FORM_OPERATOR_FIELD).click();
+ cy.get(ADD_FILTER_FORM_OPERATOR_OPTION_IS).click();
+ cy.get(ADD_FILTER_FORM_FILTER_VALUE_INPUT).type(value);
+ cy.get(ADD_FILTER_FORM_SAVE_BUTTON).click();
+};
From 42dcbfc4028a71664c475f4e41bbb091ce4f2790 Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Fri, 19 Jun 2020 08:55:29 +0200
Subject: [PATCH 16/33] [Ingest Pipelines] Encode URI component pipeline names
(#69489) (#69548)
* Properly encode URI component pipeline names
* safely URI decode to handle % case
---
.../sections/pipelines_clone/pipelines_clone.tsx | 4 +++-
.../pipelines_create/pipelines_create.tsx | 2 +-
.../sections/pipelines_edit/pipelines_edit.tsx | 6 ++++--
.../application/sections/pipelines_list/table.tsx | 5 ++++-
.../sections/shared/attempt_to_uri_decode.ts | 15 +++++++++++++++
.../public/application/sections/shared/index.ts | 7 +++++++
6 files changed, 34 insertions(+), 5 deletions(-)
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
index 4c857dc9c74f..6dc7769ac02a 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
@@ -12,6 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { SectionLoading, useKibana } from '../../../shared_imports';
import { PipelinesCreate } from '../pipelines_create';
+import { attemptToURIDecode } from '../shared';
export interface ParamProps {
sourceName: string;
@@ -25,8 +26,9 @@ export const PipelinesClone: FunctionComponent>
const { sourceName } = props.match.params;
const { services } = useKibana();
+ const decodedSourceName = attemptToURIDecode(sourceName);
const { error, data: pipeline, isLoading, isInitialRequest } = services.api.useLoadPipeline(
- decodeURIComponent(sourceName)
+ decodedSourceName
);
useEffect(() => {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
index b495e142561f..acca1c4e03f4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
@@ -50,7 +50,7 @@ export const PipelinesCreate: React.FunctionComponent {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
index e1a7e5d6d1a3..e09cf4820771 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
@@ -22,6 +22,8 @@ import { Pipeline } from '../../../../common/types';
import { useKibana, SectionLoading } from '../../../shared_imports';
import { PipelineForm } from '../../components';
+import { attemptToURIDecode } from '../shared';
+
interface MatchParams {
name: string;
}
@@ -37,7 +39,7 @@ export const PipelinesEdit: React.FunctionComponent(false);
const [saveError, setSaveError] = useState(null);
- const decodedPipelineName = decodeURI(decodeURIComponent(name));
+ const decodedPipelineName = attemptToURIDecode(name);
const { error, data: pipeline, isLoading } = services.api.useLoadPipeline(decodedPipelineName);
@@ -54,7 +56,7 @@ export const PipelinesEdit: React.FunctionComponent {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
index 0803b419bdbe..a4b67ca80f71 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
@@ -111,7 +111,10 @@ export const PipelineTable: FunctionComponent = ({
render: (name: string) => (
{name}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts
new file mode 100644
index 000000000000..fe5a0d7932cb
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const attemptToURIDecode = (value: string) => {
+ let result: string;
+ try {
+ result = decodeURI(decodeURIComponent(value));
+ } catch (e) {
+ result = value;
+ }
+ return result;
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts
new file mode 100644
index 000000000000..9326d1385138
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { attemptToURIDecode } from './attempt_to_uri_decode';
From 85ab88fd4cb2102c1f1d3246a37be40bc9473fa3 Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Fri, 19 Jun 2020 08:55:57 +0200
Subject: [PATCH 17/33] [Ingest Pipelines] Add test coverage for ingest
pipelines editor component (#69283) (#69546)
* first iteration of CIT tests
* address pr feedback
- use dot notation where we can
- use string literals instead of + concatentation
---
.../pipeline_processors_editor.helpers.ts | 36 ----
.../pipeline_processors_editor.helpers.tsx | 158 +++++++++++++++++
.../pipeline_processors_editor.test.tsx | 159 ++++++++++++++++--
.../components/add_processor_button.tsx | 4 +-
.../context_menu.tsx | 15 +-
.../pipeline_processors_editor_item.tsx | 6 +
.../processor_settings_form.tsx | 4 +-
.../common_fields/processor_type_field.tsx | 1 +
.../processors/custom.tsx | 1 +
.../components/drop_zone_button.tsx | 5 +-
.../components/private_tree.tsx | 4 +
.../processors_tree/components/tree_node.tsx | 1 +
.../processors_tree/processors_tree.tsx | 3 +-
.../pipeline_processors_editor/utils.ts | 2 +
14 files changed, 334 insertions(+), 65 deletions(-)
delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts
deleted file mode 100644
index acd61a9bbd01..000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts
+++ /dev/null
@@ -1,36 +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 { registerTestBed, TestBed } from '../../../../../../../test_utils';
-import { PipelineProcessorsEditor, Props } from '../pipeline_processors_editor.container';
-
-const testBedSetup = registerTestBed(PipelineProcessorsEditor, {
- doMountAsync: false,
-});
-
-export interface SetupResult extends TestBed {
- actions: {
- toggleOnFailure: () => void;
- };
-}
-
-export const setup = async (props: Props): Promise => {
- const testBed = await testBedSetup(props);
- const toggleOnFailure = () => {
- const { find } = testBed;
- find('pipelineEditorOnFailureToggle').simulate('click');
- };
-
- return {
- ...testBed,
- actions: { toggleOnFailure },
- };
-};
-
-type TestSubject =
- | 'pipelineEditorDoneButton'
- | 'pipelineEditorOnFailureToggle'
- | 'pipelineEditorOnFailureTree';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
new file mode 100644
index 000000000000..97c74e0b2231
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -0,0 +1,158 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+import React from 'react';
+import { registerTestBed, TestBed } from '../../../../../../../test_utils';
+import { PipelineProcessorsEditor, Props } from '../pipeline_processors_editor.container';
+
+jest.mock('@elastic/eui', () => ({
+ ...jest.requireActual('@elastic/eui'),
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(e.jsonContent);
+ }}
+ />
+ ),
+}));
+
+jest.mock('react-virtualized', () => ({
+ ...jest.requireActual('react-virtualized'),
+ AutoSizer: ({ children }: { children: any }) => (
+ {children({ height: 500, width: 500 })}
+ ),
+}));
+
+const testBedSetup = registerTestBed(PipelineProcessorsEditor, {
+ doMountAsync: false,
+});
+
+export interface SetupResult extends TestBed {
+ actions: ReturnType;
+}
+
+/**
+ * We make heavy use of "processorSelector" in these actions. They are a way to uniquely identify
+ * a processor and are a stringified version of {@link ProcessorSelector}.
+ *
+ * @remark
+ * See also {@link selectorToDataTestSubject}.
+ */
+const createActions = (testBed: TestBed) => {
+ const { find, component } = testBed;
+
+ return {
+ async addProcessor(processorsSelector: string, type: string, options: Record) {
+ find(`${processorsSelector}.addProcessorButton`).simulate('click');
+ await act(async () => {
+ find('processorTypeSelector').simulate('change', [{ value: type, label: type }]);
+ });
+ component.update();
+ await act(async () => {
+ find('processorOptionsEditor').simulate('change', {
+ jsonContent: JSON.stringify(options),
+ });
+ });
+ await act(async () => {
+ find('processorSettingsForm.submitButton').simulate('click');
+ });
+ },
+
+ removeProcessor(processorSelector: string) {
+ find(`${processorSelector}.moreMenu.button`).simulate('click');
+ find(`${processorSelector}.moreMenu.deleteButton`).simulate('click');
+ act(() => {
+ find('removeProcessorConfirmationModal.confirmModalConfirmButton').simulate('click');
+ });
+ },
+
+ moveProcessor(processorSelector: string, dropZoneSelector: string) {
+ act(() => {
+ find(`${processorSelector}.moveItemButton`).simulate('click');
+ });
+ act(() => {
+ find(dropZoneSelector).last().simulate('click');
+ });
+ component.update();
+ },
+
+ async addOnFailureProcessor(
+ processorSelector: string,
+ type: string,
+ options: Record
+ ) {
+ find(`${processorSelector}.moreMenu.button`).simulate('click');
+ find(`${processorSelector}.moreMenu.addOnFailureButton`).simulate('click');
+ await act(async () => {
+ find('processorTypeSelector').simulate('change', [{ value: type, label: type }]);
+ });
+ component.update();
+ await act(async () => {
+ find('processorOptionsEditor').simulate('change', {
+ jsonContent: JSON.stringify(options),
+ });
+ });
+ await act(async () => {
+ find('processorSettingsForm.submitButton').simulate('click');
+ });
+ },
+
+ duplicateProcessor(processorSelector: string) {
+ find(`${processorSelector}.moreMenu.button`).simulate('click');
+ act(() => {
+ find(`${processorSelector}.moreMenu.duplicateButton`).simulate('click');
+ });
+ },
+
+ startAndCancelMove(processorSelector: string) {
+ act(() => {
+ find(`${processorSelector}.moveItemButton`).simulate('click');
+ });
+ component.update();
+ act(() => {
+ find(`${processorSelector}.cancelMoveItemButton`).simulate('click');
+ });
+ },
+
+ toggleOnFailure() {
+ find('pipelineEditorOnFailureToggle').simulate('click');
+ },
+ };
+};
+
+export const setup = async (props: Props): Promise => {
+ const testBed = await testBedSetup(props);
+ return {
+ ...testBed,
+ actions: createActions(testBed),
+ };
+};
+
+type TestSubject =
+ | 'pipelineEditorDoneButton'
+ | 'pipelineEditorOnFailureToggle'
+ | 'addProcessorsButtonLevel1'
+ | 'processorSettingsForm'
+ | 'processorSettingsForm.submitButton'
+ | 'processorOptionsEditor'
+ | 'processorSettingsFormFlyout'
+ | 'processorTypeSelector'
+ | 'pipelineEditorOnFailureTree'
+ | string;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
index ad0474a94f56..15121cc71c32 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
@@ -3,8 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import { setup } from './pipeline_processors_editor.helpers';
+import { setup, SetupResult } from './pipeline_processors_editor.helpers';
import { Pipeline } from '../../../../../common/types';
const testProcessors: Pick = {
@@ -25,10 +24,20 @@ const testProcessors: Pick = {
};
describe('Pipeline Editor', () => {
- it('provides the same data out it got in if nothing changes', async () => {
- const onUpdate = jest.fn();
+ let onUpdate: jest.Mock;
+ let testBed: SetupResult;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
- await setup({
+ beforeEach(async () => {
+ onUpdate = jest.fn();
+ testBed = await setup({
value: {
...testProcessors,
},
@@ -38,7 +47,9 @@ describe('Pipeline Editor', () => {
onTestPipelineClick: jest.fn(),
esDocsBasePath: 'test',
});
+ });
+ it('provides the same data out it got in if nothing changes', () => {
const {
calls: [[arg]],
} = onUpdate.mock;
@@ -46,20 +57,134 @@ describe('Pipeline Editor', () => {
expect(arg.getData()).toEqual(testProcessors);
});
- it('toggles the on-failure processors', async () => {
- const { actions, exists } = await setup({
- value: {
- ...testProcessors,
- },
- onFlyoutOpen: jest.fn(),
- onUpdate: jest.fn(),
- isTestButtonDisabled: false,
- onTestPipelineClick: jest.fn(),
- esDocsBasePath: 'test',
- });
-
+ it('toggles the on-failure processors tree', () => {
+ const { actions, exists } = testBed;
expect(exists('pipelineEditorOnFailureTree')).toBe(false);
actions.toggleOnFailure();
expect(exists('pipelineEditorOnFailureTree')).toBe(true);
});
+
+ describe('processors', () => {
+ it('adds a new processor', async () => {
+ const { actions } = testBed;
+ await actions.addProcessor('processors', 'test', { if: '1 == 1' });
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors.length).toBe(3);
+ const [a, b, c] = processors;
+ expect(a).toEqual(testProcessors.processors[0]);
+ expect(b).toEqual(testProcessors.processors[1]);
+ expect(c).toEqual({ test: { if: '1 == 1' } });
+ });
+
+ it('removes a processor', () => {
+ const { actions } = testBed;
+ // processor>0 denotes the first processor in the top-level processors array.
+ actions.removeProcessor('processors>0');
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors.length).toBe(1);
+ expect(processors[0]).toEqual({
+ gsub: {
+ field: '_index',
+ pattern: '(.monitoring-\\w+-)6(-.+)',
+ replacement: '$17$2',
+ },
+ });
+ });
+
+ it('reorders processors', () => {
+ const { actions } = testBed;
+ actions.moveProcessor('processors>0', 'dropButtonBelow-processors>1');
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors).toEqual(testProcessors.processors.slice(0).reverse());
+ });
+
+ it('adds an on-failure processor to a processor', async () => {
+ const { actions, find, exists } = testBed;
+ const processorSelector = 'processors>1';
+ await actions.addOnFailureProcessor(processorSelector, 'test', { if: '1 == 2' });
+ // Assert that the add on failure button has been removed
+ find(`${processorSelector}.moreMenu.button`).simulate('click');
+ expect(!exists(`${processorSelector}.moreMenu.addOnFailureButton`));
+ // Assert that the add processor button is now visible
+ expect(exists(`${processorSelector}.addProcessor`));
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors.length).toBe(2);
+ expect(processors[0]).toEqual(testProcessors.processors[0]); // should be unchanged
+ expect(processors[1].gsub).toEqual({
+ ...testProcessors.processors[1].gsub,
+ on_failure: [{ test: { if: '1 == 2' } }],
+ });
+ });
+
+ it('moves a processor to a nested dropzone', async () => {
+ const { actions } = testBed;
+ await actions.addOnFailureProcessor('processors>1', 'test', { if: '1 == 3' });
+ actions.moveProcessor('processors>0', 'dropButtonBelow-processors>1>onFailure>0');
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors.length).toBe(1);
+ expect(processors[0].gsub.on_failure).toEqual([
+ {
+ test: { if: '1 == 3' },
+ },
+ testProcessors.processors[0],
+ ]);
+ });
+
+ it('duplicates a processor', async () => {
+ const { actions } = testBed;
+ await actions.addOnFailureProcessor('processors>1', 'test', { if: '1 == 4' });
+ actions.duplicateProcessor('processors>1');
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ expect(processors.length).toBe(3);
+ const duplicatedProcessor = {
+ gsub: {
+ ...testProcessors.processors[1].gsub,
+ on_failure: [{ test: { if: '1 == 4' } }],
+ },
+ };
+ expect(processors).toEqual([
+ testProcessors.processors[0],
+ duplicatedProcessor,
+ duplicatedProcessor,
+ ]);
+ });
+
+ it('can cancel a move', () => {
+ const { actions, exists } = testBed;
+ const processorSelector = 'processors>0';
+ actions.startAndCancelMove(processorSelector);
+ // Assert that we have exited move mode for this processor
+ expect(exists(`moveItemButton-${processorSelector}`));
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const { processors } = onUpdateResult.getData();
+ // Assert that nothing has changed
+ expect(processors).toEqual(testProcessors.processors);
+ });
+
+ it('moves to and from the global on-failure tree', async () => {
+ const { actions } = testBed;
+ actions.toggleOnFailure();
+ await actions.addProcessor('onFailure', 'test', { if: '1 == 5' });
+ actions.moveProcessor('processors>0', 'dropButtonBelow-onFailure>0');
+ const [onUpdateResult1] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const data1 = onUpdateResult1.getData();
+ expect(data1.processors.length).toBe(1);
+ expect(data1.on_failure.length).toBe(2);
+ expect(data1.processors).toEqual([testProcessors.processors[1]]);
+ expect(data1.on_failure).toEqual([{ test: { if: '1 == 5' } }, testProcessors.processors[0]]);
+ actions.moveProcessor('onFailure>1', 'dropButtonAbove-processors>0');
+ const [onUpdateResult2] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const data2 = onUpdateResult2.getData();
+ expect(data2.processors.length).toBe(2);
+ expect(data2.on_failure.length).toBe(1);
+ expect(data2.processors).toEqual(testProcessors.processors);
+ expect(data2.on_failure).toEqual([{ test: { if: '1 == 5' } }]);
+ });
+ });
});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
index 5f9bf87ceca1..276d684e3dca 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
@@ -13,12 +13,14 @@ export interface Props {
onClick: () => void;
}
-export const AddProcessorButton: FunctionComponent = ({ onClick }) => {
+export const AddProcessorButton: FunctionComponent = (props) => {
+ const { onClick } = props;
const {
state: { editor },
} = usePipelineProcessorsContext();
return (
void;
onDelete: () => void;
onAddOnFailure: () => void;
+ 'data-test-subj'?: string;
}
-export const ContextMenu: FunctionComponent = ({
- showAddOnFailure,
- onDuplicate,
- onAddOnFailure,
- onDelete,
- disabled,
-}) => {
+export const ContextMenu: FunctionComponent = (props) => {
+ const { showAddOnFailure, onDuplicate, onAddOnFailure, onDelete, disabled } = props;
const [isOpen, setIsOpen] = useState(false);
const contextMenuItems = [
{
@@ -40,6 +37,7 @@ export const ContextMenu: FunctionComponent = ({
,
showAddOnFailure ? (
{
@@ -51,6 +49,7 @@ export const ContextMenu: FunctionComponent = ({
) : undefined,
= ({
return (
setIsOpen(false)}
button={
setIsOpen((v) => !v)}
iconType="boxesHorizontal"
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx
index 0e47b3ef7cf8..0eb259db75f4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx
@@ -8,6 +8,7 @@ import React, { FunctionComponent, memo } from 'react';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
import { ProcessorInternal, ProcessorSelector } from '../../types';
+import { selectorToDataTestSubject } from '../../utils';
import { usePipelineProcessorsContext } from '../../context';
@@ -46,6 +47,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo(
responsive={false}
alignItems="center"
justifyContent="spaceBetween"
+ data-test-subj={selectorToDataTestSubject(selector)}
>
@@ -85,6 +87,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo(
= memo(
{selected ? (
= memo(
) : (
= memo(
{
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx
index 81b5731a96d5..84dfce64f602 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx
@@ -64,7 +64,7 @@ export const ProcessorSettingsForm: FunctionComponent = memo(
);
return (
-
{
event.preventDefault();
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
index ebe4ca4962b4..a396a7f4d5ec 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
@@ -89,6 +89,7 @@ export const TreeNode: FunctionComponent = ({
processors={processor.onFailure}
/>
onAction({
type: 'addProcessor',
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
index d0661913515b..db71cf25faac 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
@@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, keyCodes } from '@elastic/eui';
import { List, WindowScroller } from 'react-virtualized';
import { ProcessorInternal, ProcessorSelector } from '../../types';
+import { selectorToDataTestSubject } from '../../utils';
import './processors_tree.scss';
import { AddProcessorButton } from '../add_processor_button';
@@ -96,7 +97,7 @@ export const ProcessorsTree: FunctionComponent = memo((props) => {
-
+
{
onAction({ type: 'addProcessor', payload: { target: baseSelector } });
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts
index 49d24e8dc35c..43703ed7d97b 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts
@@ -6,6 +6,8 @@
import { ProcessorSelector } from './types';
+export const selectorToDataTestSubject = (selector: ProcessorSelector) => selector.join('>');
+
type Path = string[];
/**
From aaa1b43f3682bca72ccf31d99bf709d94467e354 Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Fri, 19 Jun 2020 08:56:13 +0200
Subject: [PATCH 18/33] [Console] Added license headers to worker files
(#69387) (#69551)
* added license headers to worker files
* add missing @notice
* update notice.txt
* fix notice
* remove start-end license block markers
* Added license pre-amble
---
NOTICE.txt | 62 +++++++++++++++++++
.../legacy_core_editor/mode/worker/worker.js | 62 ++++++++++++++++---
.../modes/x_json/worker/x_json.ace.worker.js | 52 ++++++++++++++++
3 files changed, 167 insertions(+), 9 deletions(-)
diff --git a/NOTICE.txt b/NOTICE.txt
index 946b328b8766..94312d46c35e 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -29,6 +29,68 @@ Author Tobias Koppers @sokra
---
This product has relied on ASTExplorer that is licensed under MIT.
+---
+This product includes code that is based on Ace editor, which was available
+under a "BSD" license.
+
+Distributed under the BSD license:
+
+Copyright (c) 2010, Ajax.org B.V.
+All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Ajax.org B.V. nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---
+This product includes code that is based on Ace editor, which was available
+under a "BSD" license.
+
+Distributed under the BSD license:
+
+Copyright (c) 2010, Ajax.org B.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Ajax.org B.V. nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
---
This product includes code that is based on flot-charts, which was available
under a "MIT" license.
diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
index 1cc626cfa668..bd5932e88b5e 100644
--- a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
+++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
@@ -1,5 +1,57 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* @notice
+ *
+ * This product includes code that is based on Ace editor, which was available
+ * under a "BSD" license.
+ *
+ * Distributed under the BSD license:
+ *
+ * Copyright (c) 2010, Ajax.org B.V.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Ajax.org B.V. nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
/* eslint-disable */
-/*
+/*
This file is loaded up as a blob by Brace to hand to Ace to load as Jsonp
(hence the redefining of everything). It is based on the javascript
mode from the brace distro.
@@ -197,7 +249,6 @@ ace.define('ace/lib/oop', ['require', 'exports', 'module'], function (
acequire,
exports
) {
-
(exports.inherits = function (ctor, superCtor) {
(ctor.super_ = superCtor),
(ctor.prototype = Object.create(superCtor.prototype, {
@@ -221,7 +272,6 @@ ace.define('ace/range', ['require', 'exports', 'module'], function (
acequire,
exports
) {
-
let comparePoints = function (p1, p2) {
return p1.row - p2.row || p1.column - p2.column;
},
@@ -426,7 +476,6 @@ ace.define('ace/apply_delta', ['require', 'exports', 'module'], function (
acequire,
exports
) {
-
exports.applyDelta = function (docLines, delta) {
let row = delta.start.row,
startColumn = delta.start.column,
@@ -467,7 +516,6 @@ ace.define(
'ace/lib/event_emitter',
['require', 'exports', 'module'],
function (acequire, exports) {
-
let EventEmitter = {},
stopPropagation = function () {
this.propagationStopped = !0;
@@ -579,7 +627,6 @@ ace.define(
'ace/anchor',
['require', 'exports', 'module', 'ace/lib/oop', 'ace/lib/event_emitter'],
function (acequire, exports) {
-
let oop = acequire('./lib/oop'),
EventEmitter = acequire('./lib/event_emitter').EventEmitter,
Anchor = (exports.Anchor = function (doc, row, column) {
@@ -696,7 +743,6 @@ ace.define(
'ace/anchor',
],
function (acequire, exports) {
-
let oop = acequire('./lib/oop'),
applyDelta = acequire('./apply_delta').applyDelta,
EventEmitter = acequire('./lib/event_emitter').EventEmitter,
@@ -1064,7 +1110,6 @@ ace.define('ace/lib/lang', ['require', 'exports', 'module'], function (
acequire,
exports
) {
-
(exports.last = function (a) {
return a[a.length - 1];
}),
@@ -1215,7 +1260,6 @@ ace.define(
'ace/lib/lang',
],
function (acequire, exports) {
-
acequire('../range').Range;
let Document = acequire('../document').Document,
lang = acequire('../lib/lang'),
diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js
index 69a8cc86f1f7..53cdb5885c73 100644
--- a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js
+++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js
@@ -1,3 +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.
+ */
+
+/* @notice
+ *
+ * This product includes code that is based on Ace editor, which was available
+ * under a "BSD" license.
+ *
+ * Distributed under the BSD license:
+ *
+ * Copyright (c) 2010, Ajax.org B.V.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Ajax.org B.V. nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
/* eslint-disable */
/*
This file is loaded up as a blob by Brace to hand to Ace to load as Jsonp
From 558616c11b0c84bfa9e6dd467b06bf40a1421d13 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Fri, 19 Jun 2020 11:41:44 -0400
Subject: [PATCH 19/33] [Security_Solution][Endpoint] Resolver leverage
ancestry array for queries (#69264) (#69619)
* Adding alerts route
* Adding related alerts generator changes, tests, and script updates
* Fixing missed parameter
* Aligning the AlertEvent and ResolverEvent definition
* Fixing type errors
* Fixing import error
* Adding ancestry functionality in generator
* Creating some tests for ancestry field
* Making progress on the ancestry
* Fixing the ancestry verification
* Fixing existing tests
* Removing unused code and fixing test
* Adding more comments
* Fixing endgame queries
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../common/endpoint/generate_data.test.ts | 36 +++--
.../common/endpoint/generate_data.ts | 48 ++++++-
.../common/endpoint/models/event.ts | 7 +
.../common/endpoint/types.ts | 14 ++
.../endpoint/routes/resolver/utils/fetch.ts | 133 +++++++++++++-----
.../api_integration/apis/endpoint/resolver.ts | 99 ++++++++-----
6 files changed, 248 insertions(+), 89 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
index d7b653916970..4516007580ed 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
@@ -10,6 +10,7 @@ import {
TreeNode,
RelatedEventCategory,
ECSCategory,
+ ANCESTRY_LIMIT,
} from './generate_data';
interface Node {
@@ -113,6 +114,7 @@ describe('data generator', () => {
relatedEvents: 0,
relatedAlerts: 0,
});
+ tree.ancestry.delete(tree.origin.id);
});
it('creates an alert for the origin node but no other nodes', () => {
@@ -159,6 +161,30 @@ describe('data generator', () => {
return (inRelated || inRelatedAlerts || inLifecycle) && event.process.entity_id === node.id;
};
+ const verifyAncestry = (event: Event, genTree: Tree) => {
+ if (event.process.Ext.ancestry.length > 0) {
+ expect(event.process.parent?.entity_id).toBe(event.process.Ext.ancestry[0]);
+ }
+ for (let i = 0; i < event.process.Ext.ancestry.length; i++) {
+ const ancestor = event.process.Ext.ancestry[i];
+ const parent = genTree.children.get(ancestor) || genTree.ancestry.get(ancestor);
+ expect(ancestor).toBe(parent?.lifecycle[0].process.entity_id);
+
+ // the next ancestor should be the grandparent
+ if (i + 1 < event.process.Ext.ancestry.length) {
+ const grandparent = event.process.Ext.ancestry[i + 1];
+ expect(grandparent).toBe(parent?.lifecycle[0].process.parent?.entity_id);
+ }
+ }
+ };
+
+ it('has ancestry array defined', () => {
+ expect(tree.origin.lifecycle[0].process.Ext.ancestry.length).toBe(ANCESTRY_LIMIT);
+ for (const event of tree.allEvents) {
+ verifyAncestry(event, tree);
+ }
+ });
+
it('has the right related events for each node', () => {
const checkRelatedEvents = (node: TreeNode) => {
expect(node.relatedEvents.length).toEqual(4);
@@ -185,8 +211,6 @@ describe('data generator', () => {
for (const node of tree.children.values()) {
checkRelatedEvents(node);
}
-
- checkRelatedEvents(tree.origin);
});
it('has the right number of related alerts for each node', () => {
@@ -202,7 +226,8 @@ describe('data generator', () => {
});
it('has the right number of ancestors', () => {
- expect(tree.ancestry.size).toEqual(ancestors);
+ // +1 for the origin node
+ expect(tree.ancestry.size).toEqual(ancestors + 1);
});
it('has the right number of total children', () => {
@@ -239,10 +264,7 @@ describe('data generator', () => {
const children = tree.children.get(event.process.entity_id);
if (children) {
expect(eventInNode(event, children)).toBeTruthy();
- return;
}
-
- expect(eventInNode(event, tree.origin)).toBeTruthy();
});
});
@@ -260,8 +282,6 @@ describe('data generator', () => {
total += nodeEventCount(node);
}
- total += nodeEventCount(tree.origin);
-
expect(tree.allEvents.length).toEqual(total);
});
});
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index ad140e48d4c6..ea3d61564011 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -18,6 +18,16 @@ import {
import { factory as policyFactory } from './models/policy_config';
export type Event = AlertEvent | EndpointEvent;
+/**
+ * This value indicates the limit for the size of the ancestry array. The endpoint currently saves up to 20 values
+ * in its messages. To simulate a limit on the array size I'm using 2 here so that we can't rely on there being a large
+ * number like 20. The ancestry array contains entity_ids for the ancestors of a particular process.
+ *
+ * The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
+ * values towards the end of the array are more distant ancestors (grandparents). Therefore
+ * ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
+ */
+export const ANCESTRY_LIMIT: number = 2;
interface EventOptions {
timestamp?: number;
@@ -26,6 +36,7 @@ interface EventOptions {
eventType?: string;
eventCategory?: string | string[];
processName?: string;
+ ancestry?: string[];
pid?: number;
parentPid?: number;
extensions?: object;
@@ -352,7 +363,8 @@ export class EndpointDocGenerator {
public generateAlert(
ts = new Date().getTime(),
entityID = this.randomString(10),
- parentEntityID?: string
+ parentEntityID?: string,
+ ancestryArray: string[] = []
): AlertEvent {
return {
...this.commonInfo,
@@ -412,6 +424,9 @@ export class EndpointDocGenerator {
sha256: 'fake sha256',
},
Ext: {
+ // simulate a finite ancestry array size, the endpoint limits the ancestry array to 20 entries we'll use
+ // 2 so that the backend can handle that case
+ ancestry: ancestryArray.slice(0, ANCESTRY_LIMIT),
code_signature: [
{
trusted: false,
@@ -532,6 +547,9 @@ export class EndpointDocGenerator {
}
: undefined,
name: processName,
+ // simulate a finite ancestry array size, the endpoint limits the ancestry array to 20 entries we'll use
+ // 2 so that the backend can handle that case
+ Ext: { ancestry: options.ancestry?.slice(0, ANCESTRY_LIMIT) || [] },
},
user: {
domain: this.randomString(10),
@@ -589,9 +607,6 @@ export class EndpointDocGenerator {
throw Error(`could not find origin while building tree: ${alert.process.entity_id}`);
}
- // remove the origin node from the ancestry array
- ancestryNodes.delete(alert.process.entity_id);
-
const children = Array.from(
this.descendantsTreeGenerator(
alert,
@@ -715,7 +730,7 @@ export class EndpointDocGenerator {
}
};
- // generate related alerts for rootW
+ // generate related alerts for root
const processDuration: number = 6 * 3600;
if (this.randomN(100) < pctWithRelated) {
addRelatedEvents(ancestor, processDuration, events);
@@ -740,6 +755,8 @@ export class EndpointDocGenerator {
ancestor = this.generateEvent({
timestamp,
parentEntityID: ancestor.process.entity_id,
+ // add the parent to the ancestry array
+ ancestry: [ancestor.process.entity_id, ...ancestor.process.Ext.ancestry],
parentPid: ancestor.process.pid,
pid: this.randomN(5000),
});
@@ -755,6 +772,7 @@ export class EndpointDocGenerator {
parentEntityID: ancestor.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
+ ancestry: ancestor.process.Ext.ancestry,
})
);
}
@@ -773,7 +791,12 @@ export class EndpointDocGenerator {
}
}
events.push(
- this.generateAlert(timestamp, ancestor.process.entity_id, ancestor.process.parent?.entity_id)
+ this.generateAlert(
+ timestamp,
+ ancestor.process.entity_id,
+ ancestor.process.parent?.entity_id,
+ ancestor.process.Ext.ancestry
+ )
);
return events;
}
@@ -828,6 +851,10 @@ export class EndpointDocGenerator {
const child = this.generateEvent({
timestamp,
parentEntityID: currentState.event.process.entity_id,
+ ancestry: [
+ currentState.event.process.entity_id,
+ ...currentState.event.process.Ext.ancestry,
+ ],
});
maxChildren = this.randomN(maxChildrenPerNode + 1);
@@ -849,6 +876,7 @@ export class EndpointDocGenerator {
parentEntityID: child.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
+ ancestry: child.process.Ext.ancestry,
});
}
if (this.randomN(100) < percentNodesWithRelated) {
@@ -893,6 +921,7 @@ export class EndpointDocGenerator {
parentEntityID: node.process.parent?.entity_id,
eventCategory: eventInfo.category,
eventType: eventInfo.creationType,
+ ancestry: node.process.Ext.ancestry,
});
}
}
@@ -911,7 +940,12 @@ export class EndpointDocGenerator {
) {
for (let i = 0; i < relatedAlerts; i++) {
const ts = node['@timestamp'] + this.randomN(alertCreationTime) * 1000;
- yield this.generateAlert(ts, node.process.entity_id, node.process.parent?.entity_id);
+ yield this.generateAlert(
+ ts,
+ node.process.entity_id,
+ node.process.parent?.entity_id,
+ node.process.Ext.ancestry
+ );
}
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts
index 3f07bf77abf2..98f4b4336a1c 100644
--- a/x-pack/plugins/security_solution/common/endpoint/models/event.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts
@@ -53,6 +53,13 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
return event.process.parent?.entity_id;
}
+export function ancestryArray(event: ResolverEvent): string[] | undefined {
+ if (isLegacyEvent(event)) {
+ return undefined;
+ }
+ return event.process.Ext.ancestry;
+}
+
/**
* @param event The event to get the category for
*/
diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts
index 0b93af741da2..f8cfb8f7c3bb 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types.ts
@@ -300,6 +300,12 @@ export interface AlertEvent {
thread?: ThreadFields[];
uptime: number;
Ext: {
+ /*
+ * The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
+ * values towards the end of the array are more distant ancestors (grandparents). Therefore
+ * ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
+ */
+ ancestry: string[];
code_signature: Array<{
subject_name: string;
trusted: boolean;
@@ -469,6 +475,14 @@ export interface EndpointEvent {
name?: string;
pid?: number;
};
+ /*
+ * The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
+ * values towards the end of the array are more distant ancestors (grandparents). Therefore
+ * ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
+ */
+ Ext: {
+ ancestry: string[];
+ };
};
user?: {
domain?: string;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
index da6d8e2cbde8..8d7c3d7b7315 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
@@ -10,8 +10,14 @@ import {
ResolverRelatedEvents,
ResolverAncestry,
ResolverRelatedAlerts,
+ LifecycleNode,
+ ResolverEvent,
} from '../../../../../common/endpoint/types';
-import { entityId, parentEntityId } from '../../../../../common/endpoint/models/event';
+import {
+ entityId,
+ ancestryArray,
+ parentEntityId,
+} from '../../../../../common/endpoint/models/event';
import { PaginationBuilder } from './pagination';
import { Tree } from './tree';
import { LifecycleQuery } from '../queries/lifecycle';
@@ -48,9 +54,21 @@ export class Fetcher {
* @param limit upper limit of ancestors to retrieve
*/
public async ancestors(limit: number): Promise {
- const root = createAncestry();
- await this.doAncestors(this.id, limit + 1, root);
- return root;
+ const ancestryInfo = createAncestry();
+ const originNode = await this.getNode(this.id);
+ if (originNode) {
+ ancestryInfo.ancestors.push(originNode);
+ // If the request is only for the origin node then set next to its parent
+ ancestryInfo.nextAncestor = parentEntityId(originNode.lifecycle[0]) || null;
+ await this.doAncestors(
+ // limit the ancestors we're looking for to the number of levels
+ // the array could be up to length 20 but that could change
+ Fetcher.getAncestryAsArray(originNode.lifecycle[0]).slice(0, limit),
+ limit,
+ ancestryInfo
+ );
+ }
+ return ancestryInfo;
}
/**
@@ -89,7 +107,26 @@ export class Fetcher {
* @param after a cursor to use as the starting point for retrieving alerts
*/
public async alerts(limit: number, after?: string): Promise {
- return this.doAlerts(limit, after);
+ const query = new AlertsQuery(
+ PaginationBuilder.createBuilder(limit, after),
+ this.indexPattern,
+ this.endpointID
+ );
+
+ const { totals, results } = await query.search(this.client, this.id);
+ if (results.length === 0) {
+ // return an empty set of results
+ return createRelatedAlerts(this.id);
+ }
+ if (!totals[this.id]) {
+ throw new Error(`Could not find the totals for related events entity_id: ${this.id}`);
+ }
+
+ return createRelatedAlerts(
+ this.id,
+ results,
+ PaginationBuilder.buildCursor(totals[this.id], results)
+ );
}
/**
@@ -102,29 +139,72 @@ export class Fetcher {
return tree;
}
+ private async getNode(entityID: string): Promise {
+ const query = new LifecycleQuery(this.indexPattern, this.endpointID);
+ const results = await query.search(this.client, entityID);
+ if (results.length === 0) {
+ return;
+ }
+
+ return createLifecycle(entityID, results);
+ }
+
+ private static getAncestryAsArray(event: ResolverEvent): string[] {
+ const ancestors = ancestryArray(event);
+ if (ancestors) {
+ return ancestors;
+ }
+
+ const parentID = parentEntityId(event);
+ if (parentID) {
+ return [parentID];
+ }
+
+ return [];
+ }
+
private async doAncestors(
- curNodeID: string,
+ ancestors: string[],
levels: number,
ancestorInfo: ResolverAncestry
): Promise {
- if (levels === 0) {
- ancestorInfo.nextAncestor = curNodeID;
+ if (levels <= 0) {
return;
}
const query = new LifecycleQuery(this.indexPattern, this.endpointID);
- const results = await query.search(this.client, curNodeID);
+ const results = await query.search(this.client, ancestors);
if (results.length === 0) {
+ ancestorInfo.nextAncestor = null;
return;
}
- ancestorInfo.ancestors.push(createLifecycle(curNodeID, results));
- const next = parentEntityId(results[0]);
- if (next === undefined) {
- return;
- }
- await this.doAncestors(next, levels - 1, ancestorInfo);
+ // bucket the start and end events together for a single node
+ const ancestryNodes = results.reduce(
+ (nodes: Map, ancestorEvent: ResolverEvent) => {
+ const nodeId = entityId(ancestorEvent);
+ let node = nodes.get(nodeId);
+ if (!node) {
+ node = createLifecycle(nodeId, []);
+ }
+
+ node.lifecycle.push(ancestorEvent);
+ return nodes.set(nodeId, node);
+ },
+ new Map()
+ );
+
+ // the order of this array is going to be weird, it will look like this
+ // [furthest grandparent...closer grandparent, next recursive call furthest grandparent...closer grandparent]
+ ancestorInfo.ancestors.push(...ancestryNodes.values());
+ ancestorInfo.nextAncestor = parentEntityId(results[0]) || null;
+ const levelsLeft = levels - ancestryNodes.size;
+ // the results come back in ascending order on timestamp so the first entry in the
+ // results should be the further ancestor (most distant grandparent)
+ const next = Fetcher.getAncestryAsArray(results[0]).slice(0, levelsLeft);
+ // the ancestry array currently only holds up to 20 values but we can't rely on that so keep recursing
+ await this.doAncestors(next, levelsLeft, ancestorInfo);
}
private async doEvents(limit: number, after?: string) {
@@ -150,29 +230,6 @@ export class Fetcher {
);
}
- private async doAlerts(limit: number, after?: string) {
- const query = new AlertsQuery(
- PaginationBuilder.createBuilder(limit, after),
- this.indexPattern,
- this.endpointID
- );
-
- const { totals, results } = await query.search(this.client, this.id);
- if (results.length === 0) {
- // return an empty set of results
- return createRelatedAlerts(this.id);
- }
- if (!totals[this.id]) {
- throw new Error(`Could not find the totals for related events entity_id: ${this.id}`);
- }
-
- return createRelatedAlerts(
- this.id,
- results,
- PaginationBuilder.buildCursor(totals[this.id], results)
- );
- }
-
private async doChildren(
cache: ChildrenNodesHelper,
ids: string[],
diff --git a/x-pack/test/api_integration/apis/endpoint/resolver.ts b/x-pack/test/api_integration/apis/endpoint/resolver.ts
index 2bb52473508b..c9356de07f71 100644
--- a/x-pack/test/api_integration/apis/endpoint/resolver.ts
+++ b/x-pack/test/api_integration/apis/endpoint/resolver.ts
@@ -41,25 +41,6 @@ const expectLifecycleNodeInMap = (node: LifecycleNode, nodeMap: Map {
- compareArrays(tree.origin.lifecycle, origin.lifecycle, true);
- verifyAncestry(ancestors, tree, verifyLastParent);
-};
-
/**
* Verify that all the ancestor nodes are valid and optionally have parents.
*
@@ -79,14 +60,59 @@ const verifyAncestry = (ancestors: LifecycleNode[], tree: Tree, verifyLastParent
expect(Object.keys(groupedAncestors).length).to.eql(ancestors.length);
// make sure there aren't any nodes with the same parent entity_id
expect(Object.keys(groupedAncestorsParent).length).to.eql(ancestors.length);
- ancestors.forEach((node) => {
+
+ // make sure each of the ancestors' lifecycle events are in the generated tree
+ for (const node of ancestors) {
+ expectLifecycleNodeInMap(node, tree.ancestry);
+ }
+
+ // start at the origin which is always the first element of the array and make sure we have a connection
+ // using parent id between each of the nodes
+ let foundParents = 0;
+ let node = ancestors[0];
+ for (let i = 0; i < ancestors.length; i++) {
const parentID = parentEntityId(node.lifecycle[0]);
- // the last node generated will have `undefined` as the parent entity_id
- if (parentID !== undefined && verifyLastParent) {
- expect(groupedAncestors[parentID]).to.be.ok();
+ if (parentID !== undefined) {
+ const nextNode = groupedAncestors[parentID];
+ if (!nextNode) {
+ break;
+ }
+ // the grouped nodes should only have a single entry since each entity is unique
+ node = nextNode[0];
}
- expectLifecycleNodeInMap(node, tree.ancestry);
- });
+ foundParents++;
+ }
+
+ if (verifyLastParent) {
+ expect(foundParents).to.eql(ancestors.length);
+ } else {
+ // if we only retrieved a portion of all the ancestors then the most distant grandparent's parent will not necessarily
+ // be in the results
+ expect(foundParents).to.eql(ancestors.length - 1);
+ }
+};
+
+/**
+ * Retrieves the most distant ancestor in the given array.
+ *
+ * @param ancestors an array of ancestor nodes
+ */
+const retrieveDistantAncestor = (ancestors: LifecycleNode[]) => {
+ // group the ancestors by their entity_id mapped to a lifecycle node
+ const groupedAncestors = _.groupBy(ancestors, (ancestor) => ancestor.entityID);
+ let node = ancestors[0];
+ for (let i = 0; i < ancestors.length; i++) {
+ const parentID = parentEntityId(node.lifecycle[0]);
+ if (parentID !== undefined) {
+ const nextNode = groupedAncestors[parentID];
+ if (nextNode) {
+ node = nextNode[0];
+ } else {
+ return node;
+ }
+ }
+ }
+ return node;
};
/**
@@ -398,6 +424,7 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
)
.expect(200);
expect(body.ancestors[0].lifecycle.length).to.eql(2);
+ expect(body.ancestors.length).to.eql(2);
expect(body.nextAncestor).to.eql(null);
});
@@ -425,9 +452,12 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
});
describe('endpoint events', () => {
- const getRootAndAncestry = (ancestry: ResolverAncestry) => {
- return { root: ancestry.ancestors[0], ancestry: ancestry.ancestors.slice(1) };
- };
+ it('should return the origin node at the front of the array', async () => {
+ const { body }: { body: ResolverAncestry } = await supertest
+ .get(`/api/endpoint/resolver/${tree.origin.id}/ancestry?ancestors=9`)
+ .expect(200);
+ expect(body.ancestors[0].entityID).to.eql(tree.origin.id);
+ });
it('should return details for the root node', async () => {
const { body }: { body: ResolverAncestry } = await supertest
@@ -435,8 +465,8 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
.expect(200);
// the tree we generated had 5 ancestors + 1 origin node
expect(body.ancestors.length).to.eql(6);
- const ancestryInfo = getRootAndAncestry(body);
- verifyAncestryFromOrigin(ancestryInfo.root, ancestryInfo.ancestry, tree, true);
+ expect(body.ancestors[0].entityID).to.eql(tree.origin.id);
+ verifyAncestry(body.ancestors, tree, true);
expect(body.nextAncestor).to.eql(null);
});
@@ -454,12 +484,9 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
.expect(200);
// it should have 2 ancestors + 1 origin
expect(body.ancestors.length).to.eql(3);
- const ancestryInfo = getRootAndAncestry(body);
- verifyAncestryFromOrigin(ancestryInfo.root, ancestryInfo.ancestry, tree, false);
- expect(body.nextAncestor).to.eql(
- // it should be the parent entity id on the last element of the ancestry array
- parentEntityId(ancestryInfo.ancestry[ancestryInfo.ancestry.length - 1].lifecycle[0])
- );
+ verifyAncestry(body.ancestors, tree, false);
+ const distantGrandparent = retrieveDistantAncestor(body.ancestors);
+ expect(body.nextAncestor).to.eql(parentEntityId(distantGrandparent.lifecycle[0]));
});
it('should handle multiple ancestor requests', async () => {
From 2329ed169c55c97197ded8d94fb9b8ad66132a7a Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Fri, 19 Jun 2020 15:12:03 -0400
Subject: [PATCH 20/33] Fixing resolver alert generation (#69587) (#69618)
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../plugins/security_solution/common/endpoint/generate_data.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index ea3d61564011..ef9e8376827a 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -667,6 +667,7 @@ export class EndpointDocGenerator {
const ancestry = this.createAlertEventAncestry(
options.ancestors,
options.relatedEvents,
+ options.relatedAlerts,
options.percentWithRelated,
options.percentTerminated
);
From 192f4e3e09e48a91d65ba64788b8e824e472f362 Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Fri, 19 Jun 2020 17:41:10 -0400
Subject: [PATCH 21/33] [Endpoint] add policy empty state (#69449) (#69616)
---
.../pages/policy/view/policy_list.test.tsx | 12 +-
.../pages/policy/view/policy_list.tsx | 168 ++++++++++++++++--
.../apps/endpoint/policy_list.ts | 53 +++---
.../page_objects/policy_page.ts | 8 +
4 files changed, 196 insertions(+), 45 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
index a2901ab6bfe5..63a5cd0d67ae 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
@@ -23,12 +23,18 @@ describe('when on the policies page', () => {
render = () => mockedContext.render( );
});
- it('should show a table', async () => {
+ it('should show the empty state', async () => {
const renderResult = render();
- const table = await renderResult.findByTestId('policyTable');
+ const table = await renderResult.findByTestId('emptyPolicyTable');
expect(table).not.toBeNull();
});
+ it('should display the onboarding steps', async () => {
+ const renderResult = render();
+ const onboardingSteps = await renderResult.findByTestId('onboardingSteps');
+ expect(onboardingSteps).not.toBeNull();
+ });
+
describe('when list data loads', () => {
let firstPolicyID: string;
beforeEach(() => {
@@ -50,11 +56,13 @@ describe('when on the policies page', () => {
});
});
});
+
it('should display rows in the table', async () => {
const renderResult = render();
const rows = await renderResult.findAllByRole('row');
expect(rows).toHaveLength(4);
});
+
it('should display policy name value as a link', async () => {
const renderResult = render();
const policyNameLink = (await renderResult.findAllByTestId('policyNameLink'))[0];
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
index 090a16a76366..2d4abd6de0e4 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, CSSProperties, useState, MouseEvent } from 'react';
import {
EuiBasicTable,
EuiText,
@@ -22,6 +22,9 @@ import {
EuiCallOut,
EuiSpacer,
EuiButton,
+ EuiSteps,
+ EuiTitle,
+ EuiProgress,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -61,6 +64,10 @@ const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({
whiteSpace: 'nowrap',
});
+const TEXT_ALIGN_CENTER: CSSProperties = Object.freeze({
+ textAlign: 'center',
+});
+
const DangerEuiContextMenuItem = styled(EuiContextMenuItem)`
color: ${(props) => props.theme.eui.textColors.danger};
`;
@@ -410,24 +417,50 @@ export const PolicyList = React.memo(() => {
}
bodyHeader={
-
-
-
+ policyItems &&
+ policyItems.length > 0 && (
+
+
+
+ )
}
>
- [...policyItems], [policyItems])}
- columns={columns}
- loading={loading}
- pagination={paginationSetup}
- onChange={handleTableChange}
- data-test-subj="policyTable"
- hasActions={false}
- />
+ {useMemo(() => {
+ return (
+ <>
+ {policyItems && policyItems.length > 0 ? (
+
+ ) : (
+
+ )}
+ >
+ );
+ }, [
+ policyItems,
+ loading,
+ isFetchingPackageInfo,
+ columns,
+ handleCreatePolicyClick,
+ handleTableChange,
+ paginationSetup,
+ ])}
>
@@ -436,6 +469,107 @@ export const PolicyList = React.memo(() => {
PolicyList.displayName = 'PolicyList';
+const EmptyPolicyTable = React.memo<{
+ loading: boolean;
+ onActionClick: (event: MouseEvent) => void;
+ actionDisabled: boolean;
+ dataTestSubj: string;
+}>(({ loading, onActionClick, actionDisabled, dataTestSubj }) => {
+ const policySteps = useMemo(
+ () => [
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepOneTitle', {
+ defaultMessage: 'Head over to Ingest Manager.',
+ }),
+ children: (
+
+
+
+ ),
+ },
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepTwoTitle', {
+ defaultMessage: 'We’ll create a recommended security policy for you.',
+ }),
+ children: (
+
+
+
+ ),
+ },
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepThreeTitle', {
+ defaultMessage: 'Enroll your agents through Fleet.',
+ }),
+ children: (
+
+
+
+ ),
+ },
+ ],
+ []
+ );
+ return (
+
+ {loading ? (
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
+});
+
+EmptyPolicyTable.displayName = 'EmptyPolicyTable';
+
const ConfirmDelete = React.memo<{
hostCount: number;
isDeleting: boolean;
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
index 1f5b6889f8fe..941a10041674 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
@@ -36,29 +36,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const createButtonTitle = await testSubjects.getVisibleText('headerCreateNewPolicyButton');
expect(createButtonTitle).to.equal('Create new policy');
});
- it('shows policy count total', async () => {
- const policyTotal = await testSubjects.getVisibleText('policyTotalCount');
- expect(policyTotal).to.equal('0 Policies');
- });
- it('has correct table headers', async () => {
- const allHeaderCells = await pageObjects.endpointPageUtils.tableHeaderVisibleText(
- 'policyTable'
- );
- expect(allHeaderCells).to.eql([
- 'Policy Name',
- 'Created By',
- 'Created Date',
- 'Last Updated By',
- 'Last Updated',
- 'Version',
- 'Actions',
- ]);
- });
- it('should show empty table results message', async () => {
- const [, [noItemsFoundMessage]] = await pageObjects.endpointPageUtils.tableData(
- 'policyTable'
- );
- expect(noItemsFoundMessage).to.equal('No items found');
+ it('shows empty state', async () => {
+ await testSubjects.existOrFail('emptyPolicyTable');
});
describe('and policies exists', () => {
@@ -76,6 +55,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
}
});
+ it('has correct table headers', async () => {
+ const allHeaderCells = await pageObjects.endpointPageUtils.tableHeaderVisibleText(
+ 'policyTable'
+ );
+ expect(allHeaderCells).to.eql([
+ 'Policy Name',
+ 'Created By',
+ 'Created Date',
+ 'Last Updated By',
+ 'Last Updated',
+ 'Version',
+ 'Actions',
+ ]);
+ });
+
it('should show policy on the list', async () => {
const [, policyRow] = await pageObjects.endpointPageUtils.tableData('policyTable');
// Validate row data with the exception of the Date columns - since those are initially
@@ -106,9 +100,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.policy.launchAndFindDeleteModal();
await testSubjects.existOrFail('policyListDeleteModal');
await pageObjects.common.clickConfirmOnModal();
- await pageObjects.endpoint.waitForTableToNotHaveData('policyTable');
- const policyTotal = await testSubjects.getVisibleText('policyTotalCount');
- expect(policyTotal).to.equal('0 Policies');
+ const emptyPolicyTable = await testSubjects.find('emptyPolicyTable');
+ expect(emptyPolicyTable).not.to.be(null);
});
});
@@ -148,5 +141,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await policyTestResources.deletePolicyByName(newPolicyName);
});
});
+
+ describe('and user clicks on page header create button', () => {
+ it('should direct users to the ingest management integrations add datasource', async () => {
+ await pageObjects.policy.navigateToPolicyList();
+ await (await pageObjects.policy.findOnboardingStartButton()).click();
+ await pageObjects.ingestManagerCreateDatasource.ensureOnCreatePageOrFail();
+ });
+ });
});
}
diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
index a2b0f9a67103..3ffd7eb032c2 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
@@ -101,5 +101,13 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
async findDatasourceEndpointCustomConfiguration(onEditPage: boolean = false) {
return await testSubjects.find(`endpointDatasourceConfig_${onEditPage ? 'edit' : 'create'}`);
},
+
+ /**
+ * Finds and returns the onboarding button displayed in empty List pages
+ */
+ async findOnboardingStartButton() {
+ await testSubjects.waitForEnabled('onboardingStartButton');
+ return await testSubjects.find('onboardingStartButton');
+ },
};
}
From f34d87a69f239c3754323681e72e62077ebf500d Mon Sep 17 00:00:00 2001
From: spalger
Date: Fri, 19 Jun 2020 14:47:57 -0700
Subject: [PATCH 22/33] skip failing suite (#69595)
(cherry picked from commit 6fe244ecba3fb9deb7829ad66ac6f0db7584bef6)
---
.../security_solution/cypress/integration/search_bar.spec.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
index 103d4d45b3a0..6428a855c848 100644
--- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
@@ -12,7 +12,8 @@ import { hostIpFilter } from '../objects/filter';
import { HOSTS_PAGE } from '../urls/navigation';
import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts';
-describe('SearchBar', () => {
+// FAILING: https://github.com/elastic/kibana/issues/69595
+describe.skip('SearchBar', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
waitForAllHostsToBeLoaded();
From 86598d34692a7605ab7520dc32ef8ac119f25364 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Fri, 19 Jun 2020 21:17:49 -0400
Subject: [PATCH 23/33] [Endpoint] Fix flaky endpoints list unit test (#69591)
(#69627)
* Fix flaky endpoints list unit test
* un-skip test
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../management/pages/endpoint_hosts/view/index.test.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 68c4e9607418..a3e2a55fc756 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -175,6 +175,9 @@ describe('when on the hosts page', () => {
status: overallStatus,
};
policyResponse.Endpoint.policy.applied.actions.push(downloadModelAction);
+ } else {
+ // Else, make sure the status of the generated action matches what was passed in
+ downloadModelAction.status = overallStatus;
}
if (
@@ -371,7 +374,7 @@ describe('when on the hosts page', () => {
});
});
- describe.skip('when showing host Policy Response panel', () => {
+ describe('when showing host Policy Response panel', () => {
let renderResult: ReturnType;
beforeEach(async () => {
coreStart.http.post.mockImplementation(async (requestOptions) => {
From 49ebf03e151ddd6e6ef8e9224f7f533db9d0c6e1 Mon Sep 17 00:00:00 2001
From: spalger
Date: Sat, 20 Jun 2020 00:26:56 -0700
Subject: [PATCH 24/33] disable pageLoadMetrics job, it's gotten really flaky
(cherry picked from commit 02a3800c933d7316ad21d966876e5141d1466cc3)
---
Jenkinsfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 763ee95ddde9..db9d218db15b 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -41,7 +41,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) {
'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9),
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'),
- 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'),
+ // 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'),
'xpack-securitySolutionCypress': { processNumber ->
whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) {
kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber)
From 2844946be96a3c196fcb9c28827f44a40199cbce Mon Sep 17 00:00:00 2001
From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
Date: Sat, 20 Jun 2020 15:05:30 -0400
Subject: [PATCH 25/33] [SECURITY] Introduce kibana nav (#68862) (#69629)
---
.../security_solution/common/constants.ts | 8 +
.../cypress/integration/detections.spec.ts | 8 +-
.../integration/detections_timeline.spec.ts | 4 +-
.../integration/ml_conditional_links.spec.ts | 26 +-
.../cypress/integration/navigation.spec.ts | 16 +-
.../signal_detection_rules.spec.ts | 4 +-
.../signal_detection_rules_custom.spec.ts | 6 +-
.../signal_detection_rules_export.spec.ts | 4 +-
.../signal_detection_rules_ml.spec.ts | 4 +-
.../signal_detection_rules_prebuilt.spec.ts | 6 +-
.../integration/url_compatibility.spec.ts | 17 +
.../cypress/integration/url_state.spec.ts | 24 +-
.../cypress/screens/security_header.ts | 4 +-
.../cypress/urls/ml_conditional_links.ts | 26 +-
.../cypress/urls/navigation.ts | 23 +-
.../security_solution/cypress/urls/state.ts | 16 +-
.../components/activity_monitor/columns.tsx | 97 -----
.../activity_monitor/index.test.tsx | 18 -
.../components/activity_monitor/index.tsx | 320 --------------
.../components/activity_monitor/types.ts | 29 --
.../alerts_histogram_panel/index.test.tsx | 9 +
.../alerts_histogram_panel/index.tsx | 25 +-
.../load_empty_prompt.test.tsx | 9 +
.../pre_packaged_rules/load_empty_prompt.tsx | 24 +-
.../rules/rule_actions_overflow/index.tsx | 4 +-
.../rules/step_rule_actions/index.tsx | 15 +-
.../security_solution/public/alerts/index.ts | 4 +-
.../detection_engine.test.tsx | 2 +
.../detection_engine/detection_engine.tsx | 26 +-
.../alerts/pages/detection_engine/index.tsx | 28 +-
.../detection_engine/rules/all/actions.tsx | 7 +-
.../detection_engine/rules/all/columns.tsx | 33 +-
.../detection_engine/rules/all/index.test.tsx | 9 +
.../detection_engine/rules/all/index.tsx | 10 +-
.../rules/create/index.test.tsx | 8 +
.../detection_engine/rules/create/index.tsx | 26 +-
.../rules/details/index.test.tsx | 2 +
.../detection_engine/rules/details/index.tsx | 31 +-
.../rules/edit/index.test.tsx | 2 +
.../detection_engine/rules/edit/index.tsx | 33 +-
.../detection_engine/rules/index.test.tsx | 8 +
.../pages/detection_engine/rules/index.tsx | 29 +-
.../detection_engine/rules/utils.test.ts | 8 +-
.../pages/detection_engine/rules/utils.ts | 40 +-
.../public/alerts/routes.tsx | 23 +-
.../security_solution/public/app/app.tsx | 142 +++---
.../public/app/home/home_navigations.tsx | 74 ++--
.../public/app/home/index.tsx | 32 +-
.../public/app/home/translations.ts | 4 +
.../security_solution/public/app/index.tsx | 32 +-
.../security_solution/public/app/routes.tsx | 6 +-
.../security_solution/public/app/types.ts | 19 +-
.../cases/components/all_cases/index.test.tsx | 12 +-
.../cases/components/all_cases/index.tsx | 34 +-
.../components/all_cases_modal/index.test.tsx | 9 +
.../components/case_view/actions.test.tsx | 7 +
.../cases/components/case_view/actions.tsx | 7 +-
.../cases/components/case_view/index.tsx | 6 +-
.../configure_cases/button.test.tsx | 11 +-
.../components/configure_cases/button.tsx | 28 +-
.../cases/components/create/index.test.tsx | 8 +-
.../public/cases/components/create/index.tsx | 17 +-
.../use_push_to_service/index.test.tsx | 9 +
.../components/use_push_to_service/index.tsx | 29 +-
.../user_action_markdown.test.tsx | 2 +-
.../user_action_tree/user_action_title.tsx | 4 +-
.../security_solution/public/cases/index.ts | 4 +-
.../public/cases/pages/case.tsx | 3 +-
.../public/cases/pages/case_details.tsx | 9 +-
.../public/cases/pages/configure_cases.tsx | 10 +-
.../public/cases/pages/create_case.tsx | 10 +-
.../public/cases/pages/index.tsx | 17 +-
.../public/cases/pages/utils.ts | 25 +-
.../security_solution/public/cases/routes.tsx | 17 +-
.../draggable_wrapper_hover_content.test.tsx | 2 +
.../event_details/event_details.test.tsx | 2 +
.../event_fields_browser.test.tsx | 2 +
.../common/components/events_viewer/index.tsx | 1 +
.../components/exceptions/viewer/index.tsx | 2 +-
.../__snapshots__/index.test.tsx.snap | 19 -
.../components/header_global/index.test.tsx | 40 --
.../common/components/header_global/index.tsx | 34 +-
.../components/header_page/index.test.tsx | 15 +-
.../common/components/header_page/index.tsx | 88 ++--
.../components/link_to/__mocks__/index.ts | 24 ++
.../common/components/link_to/helpers.test.ts | 2 +-
.../common/components/link_to/helpers.ts | 5 +-
.../public/common/components/link_to/index.ts | 44 +-
.../common/components/link_to/link_to.tsx | 126 ------
.../components/link_to/redirect_to_case.tsx | 40 +-
.../link_to/redirect_to_detection_engine.tsx | 64 +--
.../components/link_to/redirect_to_hosts.tsx | 53 +--
.../link_to/redirect_to_management.tsx | 15 -
.../link_to/redirect_to_network.tsx | 33 +-
.../link_to/redirect_to_overview.tsx | 16 +-
.../link_to/redirect_to_timelines.tsx | 33 +-
.../components/link_to/redirect_wrapper.tsx | 18 -
.../common/components/links/index.test.tsx | 23 +-
.../public/common/components/links/index.tsx | 124 ++++--
.../ml_host_conditional_container.tsx | 17 +-
.../ml_network_conditional_container.tsx | 9 +-
.../ml/links/create_explorer_link.test.ts | 2 +-
...lorer_link.ts => create_explorer_link.tsx} | 31 +-
.../__snapshots__/anomaly_score.test.tsx.snap | 77 +++-
.../create_descriptions_list.test.tsx.snap | 77 +++-
.../ml/score/create_description_list.tsx | 11 +-
.../get_anomalies_host_table_columns.tsx | 16 +-
.../get_anomalies_network_table_columns.tsx | 16 +-
.../__mocks__/use_get_url_search.ts | 9 +
.../navigation/breadcrumbs/index.test.ts | 81 ++--
.../navigation/breadcrumbs/index.ts | 47 +-
.../components/navigation/index.test.tsx | 97 +++--
.../common/components/navigation/index.tsx | 12 +-
.../navigation/tab_navigation/index.test.tsx | 29 +-
.../navigation/tab_navigation/index.tsx | 49 ++-
.../navigation/tab_navigation/types.ts | 2 +
.../common/components/navigation/types.ts | 18 +
.../components/news_feed/no_news/index.tsx | 26 +-
.../common/components/top_n/index.test.tsx | 8 +
.../common/components/top_n/top_n.test.tsx | 8 +
.../common/components/url_state/constants.ts | 4 +-
.../common/components/url_state/helpers.ts | 33 +-
.../components/url_state/index.test.tsx | 8 +-
.../url_state/index_mocked.test.tsx | 12 +-
.../components/url_state/test_dependencies.ts | 16 +-
.../common/components/url_state/types.ts | 4 +-
.../components/url_state/use_url_state.tsx | 4 +-
.../common/lib/compose/kibana_compose.tsx | 2 +-
.../public/common/utils/route/index.test.tsx | 8 +-
.../public/common/utils/route/spy_routes.tsx | 7 +-
.../public/common/utils/route/types.ts | 1 -
.../utils/timeline/use_show_timeline.tsx | 17 +-
.../public/endpoint_alerts/index.ts | 4 +-
.../public/endpoint_alerts/routes.tsx | 14 +-
.../security_solution/public/helpers.ts | 75 ++++
.../components/hosts_table/index.test.tsx | 2 +
.../uncommon_process_table/index.test.tsx | 2 +
.../security_solution/public/hosts/index.ts | 4 +-
.../hosts/pages/details/details_tabs.test.tsx | 2 +-
.../public/hosts/pages/details/index.tsx | 3 +-
.../public/hosts/pages/details/nav_tabs.tsx | 9 +-
.../public/hosts/pages/details/types.ts | 2 -
.../public/hosts/pages/details/utils.ts | 22 +-
.../public/hosts/pages/hosts.tsx | 5 +-
.../public/hosts/pages/hosts_tabs.tsx | 12 +-
.../public/hosts/pages/index.tsx | 135 +++---
.../public/hosts/pages/nav_tabs.tsx | 10 +-
.../public/hosts/pages/types.ts | 5 +-
.../security_solution/public/hosts/routes.tsx | 16 +-
.../public/management/common/constants.ts | 3 +-
.../public/management/common/routing.ts | 114 ++---
.../components/management_page_view.tsx | 21 +-
.../public/management/index.ts | 6 +-
.../pages/endpoint_hosts/routes.tsx | 14 +-
.../store/host_pagination.test.ts | 8 +-
.../endpoint_hosts/store/middleware.test.ts | 4 +-
.../view/details/host_details.tsx | 36 +-
.../endpoint_hosts/view/details/index.tsx | 22 +-
.../pages/endpoint_hosts/view/index.test.tsx | 8 +-
.../pages/endpoint_hosts/view/index.tsx | 55 ++-
.../public/management/pages/index.tsx | 13 +-
.../policy/store/policy_list/index.test.ts | 4 +-
.../configure_datasource.tsx | 7 +-
.../pages/policy/view/policy_details.test.tsx | 13 +-
.../pages/policy/view/policy_details.tsx | 15 +-
.../pages/policy/view/policy_list.test.tsx | 2 +-
.../pages/policy/view/policy_list.tsx | 28 +-
.../public/management/routes.tsx | 14 +-
.../public/management/types.ts | 4 +-
.../network/components/ip/index.test.tsx | 4 +-
.../network_http_table/index.test.tsx | 2 +
.../network_top_n_flow_table/index.test.tsx | 2 +
.../source_destination/index.test.tsx | 8 +
.../source_destination_ip.test.tsx | 2 +
.../security_solution/public/network/index.ts | 4 +-
.../public/network/pages/index.tsx | 35 +-
.../__snapshots__/index.test.tsx.snap | 4 +-
.../public/network/pages/ip_details/index.tsx | 3 +-
.../public/network/pages/ip_details/utils.ts | 25 +-
.../network/pages/navigation/nav_tabs.tsx | 9 +-
.../pages/navigation/network_routes.tsx | 12 +-
.../public/network/pages/navigation/types.ts | 3 +-
.../public/network/pages/navigation/utils.ts | 5 +-
.../public/network/pages/network.tsx | 3 +-
.../public/network/routes.tsx | 19 +-
.../alerts_by_category/index.test.tsx | 2 +-
.../components/alerts_by_category/index.tsx | 32 +-
.../components/event_counts/index.test.tsx | 2 +
.../components/events_by_dataset/index.tsx | 32 +-
.../components/overview_host/index.test.tsx | 1 +
.../components/overview_host/index.tsx | 34 +-
.../overview_network/index.test.tsx | 1 +
.../components/overview_network/index.tsx | 35 +-
.../components/recent_cases/index.tsx | 33 +-
.../recent_cases/no_cases/index.tsx | 33 +-
.../components/recent_cases/recent_cases.tsx | 25 +-
.../components/recent_timelines/index.tsx | 62 ++-
.../public/overview/index.ts | 4 +-
.../public/overview/pages/overview.tsx | 3 +-
.../public/overview/routes.tsx | 13 +-
.../security_solution/public/plugin.tsx | 404 ++++++++++++++----
.../security_solution/public/sub_plugins.ts | 34 ++
.../timelines/components/flyout/index.tsx | 2 +-
.../components/netflow/index.test.tsx | 2 +
.../components/open_timeline/index.tsx | 34 +-
.../components/open_timeline/types.ts | 1 +
.../open_timeline/use_timeline_types.tsx | 43 +-
.../components/timeline/body/index.test.tsx | 2 +
.../auditd/generic_row_renderer.test.tsx | 1 +
.../body/renderers/formatted_field.test.tsx | 1 +
.../body/renderers/formatted_field.tsx | 13 +-
.../renderers/formatted_field_helpers.tsx | 47 +-
.../body/renderers/get_row_renderer.test.tsx | 2 +
.../netflow/netflow_row_renderer.test.tsx | 2 +
.../renderers/plain_column_renderer.test.tsx | 2 +
.../suricata/suricata_details.test.tsx | 2 +
.../suricata/suricata_row_renderer.test.tsx | 2 +
.../renderers/system/generic_details.test.tsx | 2 +
.../system/generic_file_details.test.tsx | 8 +
.../system/generic_row_renderer.test.tsx | 1 +
.../body/renderers/zeek/zeek_details.test.tsx | 2 +
.../renderers/zeek/zeek_row_renderer.test.tsx | 2 +
.../components/timeline/index.test.tsx | 1 +
.../timelines/components/timeline/index.tsx | 4 +-
.../use_insert_timeline.tsx | 2 +-
.../timeline/properties/helpers.tsx | 21 +-
.../timeline/properties/index.test.tsx | 7 +-
.../components/timeline/properties/index.tsx | 14 +-
.../timeline/selectable_timeline/index.tsx | 32 +-
.../components/timeline/timeline.test.tsx | 10 +-
.../public/timelines/containers/all/index.tsx | 12 +-
.../public/timelines/index.ts | 4 +-
.../public/timelines/pages/index.tsx | 34 +-
.../timelines/pages/timelines_page.test.tsx | 6 +-
.../public/timelines/pages/timelines_page.tsx | 16 +-
.../public/timelines/routes.tsx | 13 +-
.../test/security_solution_endpoint/config.ts | 4 +-
.../page_objects/endpoint_page.ts | 7 +-
.../page_objects/policy_page.ts | 12 +-
239 files changed, 2889 insertions(+), 2329 deletions(-)
create mode 100644 x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts
delete mode 100644 x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx
delete mode 100644 x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx
delete mode 100644 x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts
delete mode 100644 x-pack/plugins/security_solution/public/common/components/header_global/__snapshots__/index.test.tsx.snap
delete mode 100644 x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/common/components/link_to/link_to.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_management.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/link_to/redirect_wrapper.tsx
rename x-pack/plugins/security_solution/public/common/components/ml/links/{create_explorer_link.ts => create_explorer_link.tsx} (55%)
create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/__mocks__/use_get_url_search.ts
create mode 100644 x-pack/plugins/security_solution/public/helpers.ts
create mode 100644 x-pack/plugins/security_solution/public/sub_plugins.ts
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index d7f271535228..0d162c068376 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -34,6 +34,14 @@ export const DEFAULT_INTERVAL_VALUE = 300000; // ms
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges';
export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51';
+export const APP_OVERVIEW_PATH = `${APP_PATH}/overview`;
+export const APP_ALERTS_PATH = `${APP_PATH}/alerts`;
+export const APP_HOSTS_PATH = `${APP_PATH}/hosts`;
+export const APP_NETWORK_PATH = `${APP_PATH}/network`;
+export const APP_TIMELINES_PATH = `${APP_PATH}/timelines`;
+export const APP_CASES_PATH = `${APP_PATH}/cases`;
+export const APP_MANAGEMENT_PATH = `${APP_PATH}/management`;
+
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
export const DEFAULT_INDEX_PATTERN = [
'apm-*-transaction*',
diff --git a/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts
index 43acdd40dadc..2e727be1fc9b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detections.spec.ts
@@ -28,13 +28,13 @@ import {
import { esArchiverLoad } from '../tasks/es_archiver';
import { loginAndWaitForPage } from '../tasks/login';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
describe('Detections', () => {
context('Closing alerts', () => {
beforeEach(() => {
esArchiverLoad('alerts');
- loginAndWaitForPage(DETECTIONS);
+ loginAndWaitForPage(ALERTS_URL);
});
it('Closes and opens alerts', () => {
@@ -161,7 +161,7 @@ describe('Detections', () => {
context('Opening alerts', () => {
beforeEach(() => {
esArchiverLoad('closed_alerts');
- loginAndWaitForPage(DETECTIONS);
+ loginAndWaitForPage(ALERTS_URL);
});
it('Open one alert when more than one closed alerts are selected', () => {
@@ -207,7 +207,7 @@ describe('Detections', () => {
context('Marking alerts as in-progress', () => {
beforeEach(() => {
esArchiverLoad('alerts');
- loginAndWaitForPage(DETECTIONS);
+ loginAndWaitForPage(ALERTS_URL);
});
it('Mark one alert in progress when more than one open alerts are selected', () => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts
index d3ddb2ad71e3..91617981ab14 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detections_timeline.spec.ts
@@ -15,12 +15,12 @@ import {
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPage } from '../tasks/login';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
describe('Detections timeline', () => {
beforeEach(() => {
esArchiverLoad('timeline_alerts');
- loginAndWaitForPage(DETECTIONS);
+ loginAndWaitForPage(ALERTS_URL);
});
afterEach(() => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
index 785df4a0c2f9..0c3424576e4c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
@@ -101,7 +101,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery);
cy.url().should(
'include',
- '/app/security#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
+ '/app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -109,7 +109,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
- "/app/security#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
+ '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -117,7 +117,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.url().should(
'include',
- "app/security#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999))"
+ 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999))'
);
});
@@ -125,7 +125,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.url().should(
'include',
- "/app/security#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
+ '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -133,7 +133,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery);
cy.url().should(
'include',
- '/app/security#/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
+ '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -141,7 +141,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery);
cy.url().should(
'include',
- "/app/security#/network/flows?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
+ '/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -149,7 +149,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery);
cy.url().should(
'include',
- '/app/security#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
+ '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -157,7 +157,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable);
cy.url().should(
'include',
- '/app/security#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
+ '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -165,7 +165,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery);
cy.url().should(
'include',
- "/app/security#/hosts/siem-windows/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
+ '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -173,7 +173,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.url().should(
'include',
- "/app/security#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -181,7 +181,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery);
cy.url().should(
'include',
- "/app/security#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -189,7 +189,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery);
cy.url().should(
'include',
- '/app/security#/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
+ '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
@@ -197,7 +197,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery);
cy.url().should(
'include',
- "/app/security#/hosts/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts
index 2014e34c1188..67b72982f44e 100644
--- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { DETECTIONS, HOSTS, NETWORK, OVERVIEW, TIMELINES } from '../screens/security_header';
+import { ALERTS, HOSTS, NETWORK, OVERVIEW, TIMELINES } from '../screens/security_header';
import { loginAndWaitForPage } from '../tasks/login';
import { navigateFromHeaderTo } from '../tasks/security_header';
@@ -16,26 +16,26 @@ describe('top-level navigation common to all pages in the Security app', () => {
});
it('navigates to the Overview page', () => {
navigateFromHeaderTo(OVERVIEW);
- cy.url().should('include', '/security#/overview');
+ cy.url().should('include', '/security/overview');
});
it('navigates to the Hosts page', () => {
navigateFromHeaderTo(HOSTS);
- cy.url().should('include', '/security#/hosts');
+ cy.url().should('include', '/security/hosts');
});
it('navigates to the Network page', () => {
navigateFromHeaderTo(NETWORK);
- cy.url().should('include', '/security#/network');
+ cy.url().should('include', '/security/network');
});
- it('navigates to the Detections page', () => {
- navigateFromHeaderTo(DETECTIONS);
- cy.url().should('include', '/security#/detections');
+ it('navigates to the Alerts page', () => {
+ navigateFromHeaderTo(ALERTS);
+ cy.url().should('include', '/security/alerts');
});
it('navigates to the Timelines page', () => {
navigateFromHeaderTo(TIMELINES);
- cy.url().should('include', '/security#/timelines');
+ cy.url().should('include', '/security/timelines');
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts
index e8f9411c149d..72a86e3ffffc 100644
--- a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules.spec.ts
@@ -26,7 +26,7 @@ import {
waitForRuleToBeActivated,
} from '../tasks/alert_detection_rules';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
describe('Detection rules', () => {
before(() => {
@@ -38,7 +38,7 @@ describe('Detection rules', () => {
});
it('Sorts by activated rules', () => {
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts
index e5cec16c48a3..48d0c2e7238c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_custom.spec.ts
@@ -62,7 +62,7 @@ import {
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
describe('Detection rules, custom', () => {
before(() => {
@@ -74,7 +74,7 @@ describe('Detection rules, custom', () => {
});
it('Creates and activates a new custom rule', () => {
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
@@ -169,7 +169,7 @@ describe('Detection rules, custom', () => {
describe('Deletes custom rules', () => {
beforeEach(() => {
esArchiverLoad('custom_rules');
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts
index 4a1299043899..edb559bf6a27 100644
--- a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_export.spec.ts
@@ -13,7 +13,7 @@ import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import { exportFirstRule } from '../tasks/alert_detection_rules';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson';
@@ -32,7 +32,7 @@ describe('Export rules', () => {
});
it('Exports a custom rule', () => {
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts
index fd2dff27ad35..3e0fc2e1b37f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_ml.spec.ts
@@ -58,7 +58,7 @@ import {
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
describe('Detection rules, machine learning', () => {
before(() => {
@@ -70,7 +70,7 @@ describe('Detection rules, machine learning', () => {
});
it('Creates and activates a new ml rule', () => {
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
diff --git a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts
index 2cd087b2ca5e..f819c91a7737 100644
--- a/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/signal_detection_rules_prebuilt.spec.ts
@@ -31,7 +31,7 @@ import {
import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
-import { DETECTIONS } from '../urls/navigation';
+import { ALERTS_URL } from '../urls/navigation';
import { totalNumberOfPrebuiltRules } from '../objects/rule';
@@ -48,7 +48,7 @@ describe('Detection rules, prebuilt rules', () => {
const expectedNumberOfRules = totalNumberOfPrebuiltRules;
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
@@ -73,7 +73,7 @@ describe('Deleting prebuilt rules', () => {
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;
esArchiverLoadEmptyKibana();
- loginAndWaitForPageWithoutDateRange(DETECTIONS);
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertDetectionRules();
diff --git a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts
new file mode 100644
index 000000000000..911fd7e0f348
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loginAndWaitForPage } from '../tasks/login';
+
+import { DETECTIONS } from '../urls/navigation';
+
+describe('URL compatibility', () => {
+ it('Redirects to Alerts from old Detections URL', () => {
+ loginAndWaitForPage(DETECTIONS);
+
+ cy.url().should('include', '/security/alerts');
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
index 425ed23bdafe..1cefa7fe73d3 100644
--- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
@@ -166,7 +166,10 @@ describe('url state', () => {
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
kqlSearch('source.ip: "10.142.0.9" {enter}');
- cy.url().should('include', `query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')`);
+ cy.url().should(
+ 'include',
+ `query=(language:kuery,query:%27source.ip:%20%2210.142.0.9%22%20%27)`
+ );
});
it('sets the url state when kql is set and check if href reflect this change', () => {
@@ -177,7 +180,7 @@ describe('url state', () => {
cy.get(NETWORK).should(
'have.attr',
'href',
- "#/link-to/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))"
+ `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`
);
});
@@ -190,12 +193,12 @@ describe('url state', () => {
cy.get(HOSTS).should(
'have.attr',
'href',
- "#/link-to/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
+ `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))`
);
cy.get(NETWORK).should(
'have.attr',
'href',
- "#/link-to/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
+ `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))`
);
cy.get(HOSTS_NAMES).first().invoke('text').should('eq', 'siem-kibana');
@@ -206,21 +209,21 @@ describe('url state', () => {
cy.get(ANOMALIES_TAB).should(
'have.attr',
'href',
- "#/hosts/siem-kibana/anomalies?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
+ "/app/security/hosts/siem-kibana/anomalies?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
);
cy.get(BREADCRUMBS)
.eq(1)
.should(
'have.attr',
'href',
- "#/link-to/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
+ `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))`
);
cy.get(BREADCRUMBS)
.eq(2)
.should(
'have.attr',
'href',
- "#/link-to/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))"
+ `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))`
);
});
@@ -231,7 +234,7 @@ describe('url state', () => {
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
});
- it('sets and reads the url state for timeline by id', () => {
+ it.skip('sets and reads the url state for timeline by id', () => {
loginAndWaitForPage(HOSTS_PAGE);
openTimeline();
executeTimelineKQL('host.name: *');
@@ -254,10 +257,11 @@ describe('url state', () => {
const newTimelineId = matched && matched.length > 0 ? matched[0] : 'null';
expect(matched).to.have.lengthOf(1);
closeTimeline();
- cy.visit('/app/kibana');
- cy.visit(`/app/security#/overview?timeline\=(id:'${newTimelineId}',isOpen:!t)`);
+ cy.visit('/app/home');
+ cy.visit(`/app/security/timelines?timeline=(id:'${newTimelineId}',isOpen:!t)`);
cy.contains('a', 'Security');
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).invoke('text').should('not.equal', 'Updating');
+ cy.get(TIMELINE_TITLE).should('be.visible');
cy.get(TIMELINE_TITLE).should('have.attr', 'value', timelineName);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/security_header.ts b/x-pack/plugins/security_solution/cypress/screens/security_header.ts
index c2dab051793c..89deeee0426d 100644
--- a/x-pack/plugins/security_solution/cypress/screens/security_header.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/security_header.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a';
+export const ALERTS = '[data-test-subj="navigation-alerts"]';
-export const DETECTIONS = '[data-test-subj="navigation-detections"]';
+export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a';
export const HOSTS = '[data-test-subj="navigation-hosts"]';
diff --git a/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts b/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts
index cfa18099e588..655418fc98bf 100644
--- a/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts
+++ b/x-pack/plugins/security_solution/cypress/urls/ml_conditional_links.ts
@@ -25,52 +25,52 @@
// Single IP with a null for the Query:
export const mlNetworkSingleIpNullKqlQuery =
- "/app/security#/ml-network/ip/127.0.0.1?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/127.0.0.1?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// Single IP with a value for the Query:
export const mlNetworkSingleIpKqlQuery =
- "/app/security#/ml-network/ip/127.0.0.1?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/127.0.0.1?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// Multiple IPs with a null for the Query:
export const mlNetworkMultipleIpNullKqlQuery =
- "/app/security#/ml-network/ip/127.0.0.1,127.0.0.2?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/127.0.0.1,127.0.0.2?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// Multiple IPs with a value for the Query:
export const mlNetworkMultipleIpKqlQuery =
- "/app/security#/ml-network/ip/127.0.0.1,127.0.0.2?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/127.0.0.1,127.0.0.2?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// $ip$ with a null Query:
export const mlNetworkNullKqlQuery =
- "/app/security#/ml-network/ip/$ip$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/$ip$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// $ip$ with a value for the Query:
export const mlNetworkKqlQuery =
- "/app/security#/ml-network/ip/$ip$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
+ "/app/siem#/ml-network/ip/$ip$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))";
// Single host name with a null for the Query:
export const mlHostSingleHostNullKqlQuery =
- "/app/security#/ml-hosts/siem-windows?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/siem-windows?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Single host name with a variable in the Query:
export const mlHostSingleHostKqlQueryVariable =
- "/app/security#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22$process.name$%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22$process.name$%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Single host name with a value for Query:
export const mlHostSingleHostKqlQuery =
- "/app/security#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/siem-windows?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Multiple host names with null for Query:
export const mlHostMultiHostNullKqlQuery =
- "/app/security#/ml-hosts/siem-windows,siem-suricata?query=!n&&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/siem-windows,siem-suricata?query=!n&&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Multiple host names with a value for Query:
export const mlHostMultiHostKqlQuery =
- "/app/security#/ml-hosts/siem-windows,siem-suricata?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/siem-windows,siem-suricata?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Undefined/null host name with a null for the KQL:
export const mlHostVariableHostNullKqlQuery =
- "/app/security#/ml-hosts/$host.name$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/$host.name$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
// Undefined/null host name but with a value for Query:
export const mlHostVariableHostKqlQuery =
- "/app/security#/ml-hosts/$host.name$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
+ "/app/siem#/ml-hosts/$host.name$?query=(language:kuery,query:'process.name%20:%20%22conhost.exe,sc.exe%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))";
diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts
index 9bfe2e9e5102..7978aebfb413 100644
--- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts
+++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts
@@ -4,16 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const CASES = '/app/security#/case';
-export const DETECTIONS = 'app/security#/detections';
-export const HOSTS_PAGE = '/app/security#/hosts/allHosts';
+export const ALERTS_URL = 'app/security/alerts';
+export const CASES = '/app/security/cases';
+export const DETECTIONS = '/app/siem#/detections';
+export const HOSTS_PAGE = '/app/security/hosts/allHosts';
export const HOSTS_PAGE_TAB_URLS = {
- allHosts: '/app/security#/hosts/allHosts',
- anomalies: '/app/security#/hosts/anomalies',
- authentications: '/app/security#/hosts/authentications',
- events: '/app/security#/hosts/events',
- uncommonProcesses: '/app/security#/hosts/uncommonProcesses',
+ allHosts: '/app/security/hosts/allHosts',
+ anomalies: '/app/security/hosts/anomalies',
+ authentications: '/app/security/hosts/authentications',
+ events: '/app/security/hosts/events',
+ uncommonProcesses: '/app/security/hosts/uncommonProcesses',
};
-export const NETWORK_PAGE = '/app/security#/network';
-export const OVERVIEW_PAGE = '/app/security#/overview';
-export const TIMELINES_PAGE = '/app/security#/timelines';
+export const NETWORK_PAGE = '/app/security/network';
+export const OVERVIEW_PAGE = '/app/security/overview';
+export const TIMELINES_PAGE = '/app/security/timelines';
diff --git a/x-pack/plugins/security_solution/cypress/urls/state.ts b/x-pack/plugins/security_solution/cypress/urls/state.ts
index 6de30fdafdaf..bdd90c21fbed 100644
--- a/x-pack/plugins/security_solution/cypress/urls/state.ts
+++ b/x-pack/plugins/security_solution/cypress/urls/state.ts
@@ -6,16 +6,16 @@
export const ABSOLUTE_DATE_RANGE = {
url:
- '/app/security#/network/?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))',
+ '/app/security/network/flows/?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))',
urlUnlinked:
- '/app/security#/network/?timerange=(global:(linkTo:!(),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(),timerange:(from:1564776209186,kind:absolute,to:1564779809186)))',
- urlKqlNetworkNetwork: `/app/security#/network/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
- urlKqlNetworkHosts: `/app/security#/network/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
- urlKqlHostsNetwork: `/app/security#/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
- urlKqlHostsHosts: `/app/security#/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
+ '/app/security/network/flows/?timerange=(global:(linkTo:!(),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(),timerange:(from:1564776209186,kind:absolute,to:1564779809186)))',
+ urlKqlNetworkNetwork: `/app/security/network/flows/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
+ urlKqlNetworkHosts: `/app/security/network/flows/?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
+ urlKqlHostsNetwork: `/app/security/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
+ urlKqlHostsHosts: `/app/security/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`,
urlHost:
- '/app/security#/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))',
+ '/app/security/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))',
urlHostNew:
- '/app/security#/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))',
+ '/app/security/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1577914409186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1577914409186)))',
};
diff --git a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx
deleted file mode 100644
index 51a5397637e7..000000000000
--- a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/columns.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-/* eslint-disable react/display-name */
-
-import {
- EuiIconTip,
- EuiLink,
- EuiTextColor,
- EuiBasicTableColumn,
- EuiTableActionsColumnType,
-} from '@elastic/eui';
-import React from 'react';
-import { getEmptyTagValue } from '../../../common/components/empty_value';
-import { ColumnTypes } from './types';
-
-const actions: EuiTableActionsColumnType['actions'] = [
- {
- available: (item: ColumnTypes) => item.status === 'Running',
- description: 'Stop',
- icon: 'stop',
- isPrimary: true,
- name: 'Stop',
- onClick: () => {},
- type: 'icon',
- },
- {
- available: (item: ColumnTypes) => item.status === 'Stopped',
- description: 'Resume',
- icon: 'play',
- isPrimary: true,
- name: 'Resume',
- onClick: () => {},
- type: 'icon',
- },
-];
-
-// Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes?
-export const columns: Array> = [
- {
- field: 'rule' as const,
- name: 'Rule',
- render: (value: ColumnTypes['rule'], _: ColumnTypes) => (
- {value.name}
- ),
- sortable: true,
- truncateText: true,
- },
- {
- field: 'ran' as const,
- name: 'Ran',
- render: (value: ColumnTypes['ran'], _: ColumnTypes) => '--',
- sortable: true,
- truncateText: true,
- },
- {
- field: 'lookedBackTo' as const,
- name: 'Looked back to',
- render: (value: ColumnTypes['lookedBackTo'], _: ColumnTypes) => '--',
- sortable: true,
- truncateText: true,
- },
- {
- field: 'status' as const,
- name: 'Status',
- sortable: true,
- truncateText: true,
- },
- {
- field: 'response' as const,
- name: 'Response',
- render: (value: ColumnTypes['response'], _: ColumnTypes) => {
- return value === undefined ? (
- getEmptyTagValue()
- ) : (
- <>
- {value === 'Fail' ? (
-
- {value}
-
- ) : (
- {value}
- )}
- >
- );
- },
- sortable: true,
- truncateText: true,
- },
- {
- actions,
- width: '40px',
- },
-];
diff --git a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx
deleted file mode 100644
index c5a4057b64ea..000000000000
--- a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.test.tsx
+++ /dev/null
@@ -1,18 +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 React from 'react';
-import { shallow } from 'enzyme';
-
-import { ActivityMonitor } from './index';
-
-describe('activity_monitor', () => {
- it('renders correctly', () => {
- const wrapper = shallow( );
-
- expect(wrapper.find('[title="Activity monitor"]')).toBeTruthy();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx
deleted file mode 100644
index c2b6b0f025e1..000000000000
--- a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/index.tsx
+++ /dev/null
@@ -1,320 +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 { EuiBasicTable, EuiPanel, EuiSpacer } from '@elastic/eui';
-import React, { useState, useCallback } from 'react';
-import { HeaderSection } from '../../../common/components/header_section';
-import {
- UtilityBar,
- UtilityBarAction,
- UtilityBarGroup,
- UtilityBarSection,
- UtilityBarText,
-} from '../../../common/components/utility_bar';
-import { columns } from './columns';
-import { ColumnTypes, PageTypes, SortTypes } from './types';
-
-export const ActivityMonitor = React.memo(() => {
- const sampleTableData: ColumnTypes[] = [
- {
- id: 1,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Running',
- },
- {
- id: 2,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Stopped',
- },
- {
- id: 3,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Fail',
- },
- {
- id: 4,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 5,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 6,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 7,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 8,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 9,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 10,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 11,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 12,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 13,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 14,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 15,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 16,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 17,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 18,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 19,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 20,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- {
- id: 21,
- rule: {
- href: '#/detections/rules/rule-details',
- name: 'Automated exfiltration',
- },
- ran: '2019-12-28 00:00:00.000-05:00',
- lookedBackTo: '2019-12-28 00:00:00.000-05:00',
- status: 'Completed',
- response: 'Success',
- },
- ];
-
- const [itemsTotalState] = useState(sampleTableData.length);
- const [pageState, setPageState] = useState({ index: 0, size: 20 });
- // const [selectedState, setSelectedState] = useState([]);
- const [sortState, setSortState] = useState({ field: 'ran', direction: 'desc' });
-
- const handleChange = useCallback(
- ({ page, sort }: { page?: PageTypes; sort?: SortTypes }) => {
- setPageState(page!);
- setSortState(sort!);
- },
- [setPageState, setSortState]
- );
-
- return (
- <>
-
-
-
-
-
-
-
- {'Showing: 39 activites'}
-
-
-
- {'Selected: 2 activities'}
-
- {'Stop selected'}
-
-
-
- {'Clear 7 filters'}
-
-
-
- {
- // @ts-ignore `Columns` interface differs from EUI's `column` type and is used all over this plugin, so ignore the differences instead of refactoring a lot of code
- }
- item.status !== 'Completed',
- selectableMessage: (selectable: boolean) =>
- selectable ? '' : 'Completed runs cannot be acted upon',
- onSelectionChange: (selectedItems: ColumnTypes[]) => {
- // setSelectedState(selectedItems);
- },
- }}
- sorting={{
- sort: sortState,
- }}
- />
-
- >
- );
-});
-ActivityMonitor.displayName = 'ActivityMonitor';
diff --git a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts b/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts
deleted file mode 100644
index 816992ff940d..000000000000
--- a/x-pack/plugins/security_solution/public/alerts/components/activity_monitor/types.ts
+++ /dev/null
@@ -1,29 +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 interface RuleTypes {
- href: string;
- name: string;
-}
-
-export interface ColumnTypes {
- id: number;
- rule: RuleTypes;
- ran: string;
- lookedBackTo: string;
- status: string;
- response?: string | undefined;
-}
-
-export interface PageTypes {
- index: number;
- size: number;
-}
-
-export interface SortTypes {
- field: keyof ColumnTypes;
- direction: 'asc' | 'desc';
-}
diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx
index 3376df76ac6e..db783e6cc2f8 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.test.tsx
@@ -9,6 +9,15 @@ import { shallow } from 'enzyme';
import { AlertsHistogramPanel } from './index';
+jest.mock('react-router-dom', () => {
+ const originalModule = jest.requireActual('react-router-dom');
+ return {
+ ...originalModule,
+ createHref: jest.fn(),
+ useHistory: jest.fn(),
+ };
+});
+
jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/components/navigation/use_get_url_search');
diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx
index ed98a3777557..e6eb8afc1658 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Position } from '@elastic/charts';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSelect, EuiPanel } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiSelect, EuiPanel } from '@elastic/eui';
import numeral from '@elastic/numeral';
import React, { memo, useCallback, useMemo, useState, useEffect } from 'react';
+import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { isEmpty } from 'lodash/fp';
import uuid from 'uuid';
@@ -18,19 +19,19 @@ import { escapeDataProviderId } from '../../../common/components/drag_and_drop/h
import { HeaderSection } from '../../../common/components/header_section';
import { Filter, esQuery, Query } from '../../../../../../../src/plugins/data/public';
import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query';
-import { getDetectionEngineUrl } from '../../../common/components/link_to';
+import { getDetectionEngineUrl, useFormatUrl } from '../../../common/components/link_to';
import { defaultLegendColors } from '../../../common/components/matrix_histogram/utils';
import { InspectButtonContainer } from '../../../common/components/inspect';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
import { MatrixLoader } from '../../../common/components/matrix_histogram/matrix_loader';
import { MatrixHistogramOption } from '../../../common/components/matrix_histogram/types';
import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
-import { navTabs } from '../../../app/home/home_navigations';
import { alertsHistogramOptions } from './config';
import { formatAlertsData, getAlertsHistogramQuery, showInitialLoadingSpinner } from './helpers';
import { AlertsHistogram } from './alerts_histogram';
import * as i18n from './translations';
import { RegisterQuery, AlertsHistogramOption, AlertsAggregation, AlertsTotal } from './types';
+import { LinkButton } from '../../../common/components/links';
+import { SecurityPageName } from '../../../app/types';
const DEFAULT_PANEL_HEIGHT = 300;
@@ -102,6 +103,7 @@ export const AlertsHistogramPanel = memo(
title = i18n.HISTOGRAM_HEADER,
updateDateRange,
}) => {
+ const history = useHistory();
// create a unique, but stable (across re-renders) query id
const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []);
const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -122,7 +124,7 @@ export const AlertsHistogramPanel = memo(
signalIndexName
);
const kibana = useKibana();
- const urlSearch = useGetUrlSearch(navTabs.detections);
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.alerts);
const totalAlerts = useMemo(
() =>
@@ -142,6 +144,13 @@ export const AlertsHistogramPanel = memo(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ const goToDetectionEngine = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getDetectionEngineUrl(urlSearch));
+ },
+ [history, urlSearch]
+ );
const formattedAlertsData = useMemo(() => formatAlertsData(alertsData), [alertsData]);
const legendItems: LegendItem[] = useMemo(
@@ -231,11 +240,13 @@ export const AlertsHistogramPanel = memo(
if (showLinkToAlerts) {
return (
- {i18n.VIEW_ALERTS}
+
+ {i18n.VIEW_ALERTS}
+
);
}
- }, [showLinkToAlerts, urlSearch]);
+ }, [showLinkToAlerts, goToDetectionEngine, formatUrl]);
const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [
onlyField,
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
index 8ace42fc5c3f..77c7efec262f 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
@@ -9,6 +9,15 @@ import { shallow } from 'enzyme';
import { PrePackagedRulesPrompt } from './load_empty_prompt';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../../common/components/link_to');
+
describe('PrePackagedRulesPrompt', () => {
it('renders correctly', () => {
const wrapper = shallow(
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx
index cd88c4ce72af..d82b930210ec 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.tsx
@@ -8,8 +8,12 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/e
import React, { memo, useCallback } from 'react';
import styled from 'styled-components';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../common/components/link_to/redirect_to_detection_engine';
+import { useHistory } from 'react-router-dom';
+import { getCreateRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine';
import * as i18n from './translations';
+import { LinkButton } from '../../../../common/components/links';
+import { SecurityPageName } from '../../../../app/types';
+import { useFormatUrl } from '../../../../common/components/link_to';
const EmptyPrompt = styled(EuiEmptyPrompt)`
align-self: center; /* Corrects horizontal centering in IE11 */
@@ -28,9 +32,20 @@ const PrePackagedRulesPromptComponent: React.FC = (
loading = false,
userHasNoPermissions = true,
}) => {
+ const history = useHistory();
const handlePreBuiltCreation = useCallback(() => {
createPrePackagedRules();
}, [createPrePackagedRules]);
+ const { formatUrl } = useFormatUrl(SecurityPageName.alerts);
+
+ const goToCreateRule = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getCreateRuleUrl());
+ },
+ [history]
+ );
+
return (
= (
-
{i18n.CREATE_RULE_ACTION}
-
+
}
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx
index 66f04c9bc6ad..7be50552c34d 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/index.tsx
@@ -25,7 +25,7 @@ import {
duplicateRulesAction,
} from '../../../pages/detection_engine/rules/all/actions';
import { GenericDownloader } from '../../../../common/components/generic_downloader';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../common/components/link_to/redirect_to_detection_engine';
+import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine';
const MyEuiButtonIcon = styled(EuiButtonIcon)`
&.euiButtonIcon {
@@ -56,7 +56,7 @@ const RuleActionsOverflowComponent = ({
const [, dispatchToaster] = useStateToaster();
const onRuleDeletedCallback = useCallback(() => {
- history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules`);
+ history.push(getRulesUrl());
}, [history]);
const actions = useMemo(
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
index 9334bd59bebf..061b8b0f8c36 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
@@ -34,6 +34,8 @@ import { RuleActionsField } from '../rule_actions_field';
import { useKibana } from '../../../../common/lib/kibana';
import { getSchema } from './schema';
import * as I18n from './translations';
+import { APP_ID } from '../../../../../common/constants';
+import { SecurityPageName } from '../../../../app/types';
interface StepRuleActionsProps extends RuleStepProps {
defaultValues?: ActionsStepRule | null;
@@ -84,9 +86,16 @@ const StepRuleActionsComponent: FC = ({
schema,
});
- const kibanaAbsoluteUrl = useMemo(() => application.getUrlForApp('siem', { absolute: true }), [
- application,
- ]);
+ // TO DO need to make sure that logic is still valid
+ const kibanaAbsoluteUrl = useMemo(() => {
+ const url = application.getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ absolute: true,
+ });
+ if (url != null && url.includes('app/security/alerts')) {
+ return url.replace('app/security/alerts', 'app/security');
+ }
+ return url;
+ }, [application]);
const onSubmit = useCallback(
async (enabled: boolean) => {
diff --git a/x-pack/plugins/security_solution/public/alerts/index.ts b/x-pack/plugins/security_solution/public/alerts/index.ts
index 1409ad4f5469..a2e377a73293 100644
--- a/x-pack/plugins/security_solution/public/alerts/index.ts
+++ b/x-pack/plugins/security_solution/public/alerts/index.ts
@@ -7,7 +7,7 @@
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
-import { getAlertsRoutes } from './routes';
+import { AlertsRoutes } from './routes';
import { SecuritySubPlugin } from '../app/types';
const ALERTS_TIMELINE_IDS: TimelineIdLiteral[] = [
@@ -20,7 +20,7 @@ export class Alerts {
public start(storage: Storage): SecuritySubPlugin {
return {
- routes: getAlertsRoutes(),
+ SubPluginRoutes: AlertsRoutes,
storageTimelines: {
timelineById: getTimelinesInStorageByIds(storage, ALERTS_TIMELINE_IDS),
},
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx
index de8a73283972..62b942d03591 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx
@@ -15,12 +15,14 @@ import { useUserInfo } from '../../components/user_info';
jest.mock('../../components/user_info');
jest.mock('../../../common/lib/kibana');
+jest.mock('../../../common/components/link_to');
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
return {
...originalModule,
useParams: jest.fn(),
+ useHistory: jest.fn(),
};
});
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx
index 9e1c9db168bd..05a0b4441bb3 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx
@@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButton, EuiSpacer } from '@elastic/eui';
+import { EuiSpacer } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { StickyContainer } from 'react-sticky';
import { connect, ConnectedProps } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import { SecurityPageName } from '../../../app/types';
import { TimelineId } from '../../../../common/types/timeline';
import { GlobalTime } from '../../../common/containers/global_time';
import {
@@ -37,6 +39,8 @@ import { DetectionEngineNoIndex } from './detection_engine_no_signal_index';
import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page';
import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated';
import * as i18n from './translations';
+import { LinkButton } from '../../../common/components/links';
+import { useFormatUrl } from '../../../common/components/link_to';
export const DetectionEnginePageComponent: React.FC = ({
filters,
@@ -52,8 +56,9 @@ export const DetectionEnginePageComponent: React.FC = ({
signalIndexName,
hasIndexWrite,
} = useUserInfo();
-
+ const history = useHistory();
const [lastAlerts] = useAlertInfo({});
+ const { formatUrl } = useFormatUrl(SecurityPageName.alerts);
const updateDateRangeCallback = useCallback(
({ x }) => {
@@ -66,6 +71,14 @@ export const DetectionEnginePageComponent: React.FC = ({
[setAbsoluteRangeDatePicker]
);
+ const goToRules = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getRulesUrl());
+ },
+ [history]
+ );
+
const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [
signalIndexName,
]);
@@ -111,14 +124,15 @@ export const DetectionEnginePageComponent: React.FC = ({
}
title={i18n.PAGE_TITLE}
>
-
{i18n.BUTTON_MANAGE_RULES}
-
+
@@ -161,7 +175,7 @@ export const DetectionEnginePageComponent: React.FC = ({
);
}}
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/index.tsx
index 1f9b1373d404..914734aba4ec 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/index.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
+import { Route, Switch, RouteComponentProps } from 'react-router-dom';
import { ManageUserInfo } from '../../components/user_info';
import { CreateRulePage } from './rules/create';
@@ -14,34 +14,26 @@ import { EditRulePage } from './rules/edit';
import { RuleDetailsPage } from './rules/details';
import { RulesPage } from './rules';
-const detectionEnginePath = `/:pageName(detections)`;
-
type Props = Partial> & { url: string };
const DetectionEngineContainerComponent: React.FC = () => (
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
- (
-
- )}
- />
);
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx
index d414321e4b77..5169ff009d63 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/actions.tsx
@@ -7,14 +7,14 @@
import * as H from 'history';
import React, { Dispatch } from 'react';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine';
import {
deleteRules,
duplicateRules,
enableRules,
Rule,
} from '../../../../../alerts/containers/detection_engine/rules';
-import { Action } from './reducer';
+
+import { getEditRuleUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine';
import {
ActionToaster,
@@ -26,9 +26,10 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t
import * as i18n from '../translations';
import { bucketRulesResponse } from './helpers';
+import { Action } from './reducer';
export const editRuleAction = (rule: Rule, history: H.History) => {
- history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`);
+ history.push(getEditRuleUrl(rule.id));
};
export const duplicateRulesAction = async (
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx
index cf8a3fdb6f1e..030f510b7aa3 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/columns.tsx
@@ -8,7 +8,6 @@
import {
EuiBadge,
- EuiLink,
EuiBasicTableColumn,
EuiTableActionsColumnType,
EuiText,
@@ -39,6 +38,7 @@ import {
import { Action } from './reducer';
import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip';
import * as detectionI18n from '../../translations';
+import { LinkAnchor } from '../../../../../common/components/links';
export const getActions = (
dispatch: React.Dispatch,
@@ -87,10 +87,11 @@ export type RuleStatusRowItemType = RuleStatus & {
};
export type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType;
export type RulesStatusesColumns = EuiBasicTableColumn;
-
+type FormatUrl = (path: string) => string;
interface GetColumns {
dispatch: React.Dispatch;
dispatchToaster: Dispatch;
+ formatUrl: FormatUrl;
history: H.History;
hasMlPermissions: boolean;
hasNoPermissions: boolean;
@@ -102,6 +103,7 @@ interface GetColumns {
export const getColumns = ({
dispatch,
dispatchToaster,
+ formatUrl,
history,
hasMlPermissions,
hasNoPermissions,
@@ -113,9 +115,16 @@ export const getColumns = ({
field: 'name',
name: i18n.COLUMN_RULE,
render: (value: Rule['name'], item: Rule) => (
-
+ void }) => {
+ ev.preventDefault();
+ history.push(getRuleDetailsUrl(item.id));
+ }}
+ href={formatUrl(getRuleDetailsUrl(item.id))}
+ >
{value}
-
+
),
truncateText: true,
width: '24%',
@@ -222,16 +231,26 @@ export const getColumns = ({
return hasNoPermissions ? cols : [...cols, ...actions];
};
-export const getMonitoringColumns = (): RulesStatusesColumns[] => {
+export const getMonitoringColumns = (
+ history: H.History,
+ formatUrl: FormatUrl
+): RulesStatusesColumns[] => {
const cols: RulesStatusesColumns[] = [
{
field: 'name',
name: i18n.COLUMN_RULE,
render: (value: RuleStatus['current_status']['status'], item: RuleStatusRowItemType) => {
return (
-
+ void }) => {
+ ev.preventDefault();
+ history.push(getRuleDetailsUrl(item.id));
+ }}
+ href={formatUrl(getRuleDetailsUrl(item.id))}
+ >
{value}
-
+
);
},
truncateText: true,
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx
index 11909ae7d9c5..363550f1682e 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.test.tsx
@@ -13,6 +13,15 @@ import { TestProviders } from '../../../../../common/mock';
import { wait } from '../../../../../common/lib/helpers';
import { AllRules } from './index';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../../../common/components/link_to');
+
jest.mock('./reducer', () => {
return {
allRulesReducer: jest.fn().mockReturnValue(() => ({
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx
index 80ef5681189c..65f7bb63c74e 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/index.tsx
@@ -49,6 +49,8 @@ import { allRulesReducer, State } from './reducer';
import { RulesTableFilters } from './rules_table_filters/rules_table_filters';
import { useMlCapabilities } from '../../../../../common/components/ml_popover/hooks/use_ml_capabilities';
import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions';
+import { SecurityPageName } from '../../../../../app/types';
+import { useFormatUrl } from '../../../../../common/components/link_to';
const SORT_FIELD = 'enabled';
const initialState: State = {
@@ -140,6 +142,7 @@ export const AllRules = React.memo(
const [, dispatchToaster] = useStateToaster();
const mlCapabilities = useMlCapabilities();
const [allRulesTab, setAllRulesTab] = useState(AllRulesTabs.rules);
+ const { formatUrl } = useFormatUrl(SecurityPageName.alerts);
// TODO: Refactor license check + hasMlAdminPermissions to common check
const hasMlPermissions =
@@ -225,6 +228,7 @@ export const AllRules = React.memo(
return getColumns({
dispatch,
dispatchToaster,
+ formatUrl,
history,
hasMlPermissions,
hasNoPermissions,
@@ -239,6 +243,7 @@ export const AllRules = React.memo(
}, [
dispatch,
dispatchToaster,
+ formatUrl,
hasMlPermissions,
history,
loadingRuleIds,
@@ -246,7 +251,10 @@ export const AllRules = React.memo(
reFetchRulesData,
]);
- const monitoringColumns = useMemo(() => getMonitoringColumns(), []);
+ const monitoringColumns = useMemo(() => getMonitoringColumns(history, formatUrl), [
+ history,
+ formatUrl,
+ ]);
useEffect(() => {
if (reFetchRulesData != null) {
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
index 7749e38578e9..cc39a26a8fac 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
@@ -11,6 +11,14 @@ import { TestProviders } from '../../../../../common/mock';
import { CreateRulePage } from './index';
import { useUserInfo } from '../../../../components/user_info';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
describe('CreateRulePage', () => {
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx
index de7c99acee6e..de3e23b11aaf 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx
@@ -6,12 +6,15 @@
import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui';
import React, { useCallback, useRef, useState, useMemo } from 'react';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import styled, { StyledComponent } from 'styled-components';
import { usePersistRule } from '../../../../../alerts/containers/detection_engine/rules';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine';
+import {
+ getRulesUrl,
+ getDetectionEngineUrl,
+} from '../../../../../common/components/link_to/redirect_to_detection_engine';
import { WrapperPage } from '../../../../../common/components/wrapper_page';
import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters';
import { SpyRoute } from '../../../../../common/utils/route/spy_routes';
@@ -35,6 +38,7 @@ import {
} from '../types';
import { formatRule } from './helpers';
import * as i18n from './translations';
+import { SecurityPageName } from '../../../../../app/types';
const stepsRuleOrder = [
RuleStep.defineRule,
@@ -117,6 +121,7 @@ const CreateRulePageComponent: React.FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
[stepsData.current['define-rule'].data]
);
+ const history = useHistory();
const setStepData = useCallback(
(step: RuleStep, data: unknown, isValid: boolean) => {
@@ -269,20 +274,27 @@ const CreateRulePageComponent: React.FC = () => {
if (isSaved) {
const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name;
displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster);
- return ;
+ history.replace(getRulesUrl());
+ return null;
}
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
- return ;
+ history.replace(getDetectionEngineUrl());
+ return null;
} else if (userHasNoPermissions(canUserCRUD)) {
- return ;
+ history.replace(getRulesUrl());
+ return null;
}
return (
<>
{
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx
index d755f972f295..df6ea65ba52b 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx
@@ -14,6 +14,7 @@ import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/a
import { useUserInfo } from '../../../../components/user_info';
import { useParams } from 'react-router-dom';
+jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
@@ -21,6 +22,7 @@ jest.mock('react-router-dom', () => {
return {
...originalModule,
useParams: jest.fn(),
+ useHistory: jest.fn(),
};
});
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx
index d06103bd887f..90fd4bb225ec 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx
@@ -9,7 +9,6 @@
// TODO: Disabling complexity is temporary till this component is refactored as part of lists UI integration
import {
- EuiButton,
EuiLoadingSpinner,
EuiFlexGroup,
EuiFlexItem,
@@ -20,7 +19,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { FC, memo, useCallback, useMemo, useState } from 'react';
-import { Redirect, useParams } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';
import { connect, ConnectedProps } from 'react-redux';
@@ -31,7 +30,7 @@ import { FormattedDate } from '../../../../../common/components/formatted_date';
import {
getEditRuleUrl,
getRulesUrl,
- DETECTION_ENGINE_PAGE_NAME,
+ getDetectionEngineUrl,
} from '../../../../../common/components/link_to/redirect_to_detection_engine';
import { SiemSearchBar } from '../../../../../common/components/search_bar';
import { WrapperPage } from '../../../../../common/components/wrapper_page';
@@ -73,6 +72,9 @@ import { FailureHistory } from './failure_history';
import { RuleStatus } from '../../../../components/rules//rule_status';
import { useMlCapabilities } from '../../../../../common/components/ml_popover/hooks/use_ml_capabilities';
import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions';
+import { SecurityPageName } from '../../../../../app/types';
+import { LinkButton } from '../../../../../common/components/links';
+import { useFormatUrl } from '../../../../../common/components/link_to';
import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer';
import { ExceptionListType } from '../../../../../common/components/exceptions/types';
@@ -130,6 +132,8 @@ export const RuleDetailsPageComponent: FC = ({
};
const [lastAlerts] = useAlertInfo({ ruleId });
const mlCapabilities = useMlCapabilities();
+ const history = useHistory();
+ const { formatUrl } = useFormatUrl(SecurityPageName.alerts);
// TODO: Refactor license check + hasMlAdminPermissions to common check
const hasMlPermissions =
@@ -243,8 +247,17 @@ export const RuleDetailsPageComponent: FC = ({
[ruleEnabled, setRuleEnabled]
);
+ const goToEditRule = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getEditRuleUrl(ruleId ?? ''));
+ },
+ [history, ruleId]
+ );
+
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
- return ;
+ history.replace(getDetectionEngineUrl());
+ return null;
}
return (
@@ -266,6 +279,7 @@ export const RuleDetailsPageComponent: FC = ({
backOptions={{
href: getRulesUrl(),
text: i18n.BACK_TO_RULES,
+ pageId: SecurityPageName.alerts,
}}
border
subtitle={subTitle}
@@ -309,13 +323,14 @@ export const RuleDetailsPageComponent: FC = ({
-
{ruleI18n.EDIT_RULE_SETTINGS}
-
+
= ({
}}
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx
index 91bc2ce7bce2..d754329bdd97 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.test.tsx
@@ -12,12 +12,14 @@ import { EditRulePage } from './index';
import { useUserInfo } from '../../../../components/user_info';
import { useParams } from 'react-router-dom';
+jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
return {
...originalModule,
+ useHistory: jest.fn(),
useParams: jest.fn(),
};
});
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx
index 73e76165183c..ba7444d8e8a5 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/edit/index.tsx
@@ -17,11 +17,14 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { Redirect, useParams } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { useRule, usePersistRule } from '../../../../../alerts/containers/detection_engine/rules';
import { WrapperPage } from '../../../../../common/components/wrapper_page';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../common/components/link_to/redirect_to_detection_engine';
+import {
+ getRuleDetailsUrl,
+ getDetectionEngineUrl,
+} from '../../../../../common/components/link_to/redirect_to_detection_engine';
import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters';
import { SpyRoute } from '../../../../../common/utils/route/spy_routes';
import { useUserInfo } from '../../../../components/user_info';
@@ -48,6 +51,7 @@ import {
ActionsStepRule,
} from '../types';
import * as i18n from './translations';
+import { SecurityPageName } from '../../../../../app/types';
interface StepRuleForm {
isValid: boolean;
@@ -67,6 +71,7 @@ interface ActionsStepRuleForm extends StepRuleForm {
}
const EditRulePageComponent: FC = () => {
+ const history = useHistory();
const [, dispatchToaster] = useStateToaster();
const {
loading: initLoading,
@@ -328,6 +333,14 @@ const EditRulePageComponent: FC = () => {
[selectedTab, stepsForm.current]
);
+ const goToDetailsRule = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.replace(getRuleDetailsUrl(ruleId ?? ''));
+ },
+ [history, ruleId]
+ );
+
useEffect(() => {
if (rule != null) {
const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } = getStepsData({
@@ -348,13 +361,16 @@ const EditRulePageComponent: FC = () => {
if (isSaved) {
displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster);
- return ;
+ history.replace(getRuleDetailsUrl(ruleId ?? ''));
+ return null;
}
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
- return ;
+ history.replace(getDetectionEngineUrl());
+ return null;
} else if (userHasNoPermissions(canUserCRUD)) {
- return ;
+ history.replace(getRuleDetailsUrl(ruleId ?? ''));
+ return null;
}
return (
@@ -362,8 +378,9 @@ const EditRulePageComponent: FC = () => {
{
responsive={false}
>
-
+
{i18n.CANCEL}
@@ -429,7 +446,7 @@ const EditRulePageComponent: FC = () => {
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
index 29f875d113a4..1c947deb95f9 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
@@ -11,6 +11,14 @@ import { RulesPage } from './index';
import { useUserInfo } from '../../../components/user_info';
import { usePrePackagedRules } from '../../../../alerts/containers/detection_engine/rules';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../../common/components/link_to');
jest.mock('../../../components/user_info');
jest.mock('../../../../alerts/containers/detection_engine/rules');
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx
index 4d36a2478172..7684f710952e 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.tsx
@@ -6,14 +6,13 @@
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useCallback, useRef, useState } from 'react';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import {
usePrePackagedRules,
importRules,
} from '../../../../alerts/containers/detection_engine/rules';
import {
- DETECTION_ENGINE_PAGE_NAME,
getDetectionEngineUrl,
getCreateRuleUrl,
} from '../../../../common/components/link_to/redirect_to_detection_engine';
@@ -28,10 +27,14 @@ import { ReadOnlyCallOut } from '../../../components/rules/read_only_callout';
import { UpdatePrePackagedRulesCallOut } from '../../../components/rules/pre_packaged_rules/update_callout';
import { getPrePackagedRuleStatus, redirectToDetections, userHasNoPermissions } from './helpers';
import * as i18n from './translations';
+import { SecurityPageName } from '../../../../app/types';
+import { LinkButton } from '../../../../common/components/links';
+import { useFormatUrl } from '../../../../common/components/link_to';
type Func = (refreshPrePackagedRule?: boolean) => void;
const RulesPageComponent: React.FC = () => {
+ const history = useHistory();
const [showImportModal, setShowImportModal] = useState(false);
const refreshRulesData = useRef(null);
const {
@@ -63,6 +66,7 @@ const RulesPageComponent: React.FC = () => {
rulesNotInstalled,
rulesNotUpdated
);
+ const { formatUrl } = useFormatUrl(SecurityPageName.alerts);
const handleRefreshRules = useCallback(async () => {
if (refreshRulesData.current != null) {
@@ -87,8 +91,17 @@ const RulesPageComponent: React.FC = () => {
refreshRulesData.current = refreshRule;
}, []);
+ const goToNewRule = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getCreateRuleUrl());
+ },
+ [history]
+ );
+
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
- return ;
+ history.replace(getDetectionEngineUrl());
+ return null;
}
return (
@@ -114,6 +127,7 @@ const RulesPageComponent: React.FC = () => {
backOptions={{
href: getDetectionEngineUrl(),
text: i18n.BACK_TO_ALERTS,
+ pageId: SecurityPageName.alerts,
}}
title={i18n.PAGE_TITLE}
>
@@ -155,15 +169,16 @@ const RulesPageComponent: React.FC = () => {
-
{i18n.ADD_NEW_RULE}
-
+
@@ -188,7 +203,7 @@ const RulesPageComponent: React.FC = () => {
/>
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts
index 3d2f2dc03946..91de1467a831 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.test.ts
@@ -6,6 +6,9 @@
import { getBreadcrumbs } from './utils';
+const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) =>
+ `${appId}${options?.path ?? ''}`;
+
describe('getBreadcrumbs', () => {
it('returns default value for incorrect params', () => {
expect(
@@ -17,8 +20,9 @@ describe('getBreadcrumbs', () => {
search: '',
pathName: 'pathName',
},
- []
+ [],
+ getUrlForAppMock
)
- ).toEqual([{ href: '#/link-to/detections', text: 'Alerts' }]);
+ ).toEqual([{ href: 'securitySolution:alerts', text: 'Alerts' }]);
});
});
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts
index e5cdbd7123ff..203a93acd849 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/utils.ts
@@ -9,7 +9,6 @@ import { isEmpty } from 'lodash/fp';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ChromeBreadcrumb } from '../../../../../../../../src/core/public';
import {
- getDetectionEngineUrl,
getDetectionEngineTabUrl,
getRulesUrl,
getRuleDetailsUrl,
@@ -19,21 +18,28 @@ import {
import * as i18nDetections from '../translations';
import * as i18nRules from './translations';
import { RouteSpyState } from '../../../../common/utils/route/types';
+import { GetUrlForApp } from '../../../../common/components/navigation/types';
+import { SecurityPageName } from '../../../../app/types';
+import { APP_ID } from '../../../../../common/constants';
-const getTabBreadcrumb = (pathname: string, search: string[]) => {
- const tabPath = pathname.split('/')[2];
+const getTabBreadcrumb = (pathname: string, search: string[], getUrlForApp: GetUrlForApp) => {
+ const tabPath = pathname.split('/')[1];
if (tabPath === 'alerts') {
return {
text: i18nDetections.ALERT,
- href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getDetectionEngineTabUrl(tabPath, !isEmpty(search[0]) ? search[0] : ''),
+ }),
};
}
if (tabPath === 'rules') {
return {
text: i18nRules.PAGE_TITLE,
- href: `${getRulesUrl()}${!isEmpty(search[0]) ? search[0] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getRulesUrl(!isEmpty(search[0]) ? search[0] : ''),
+ }),
};
}
};
@@ -44,15 +50,21 @@ const isRuleCreatePage = (pathname: string) =>
const isRuleEditPage = (pathname: string) =>
pathname.includes('/rules') && pathname.includes('/edit');
-export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeBreadcrumb[] => {
+export const getBreadcrumbs = (
+ params: RouteSpyState,
+ search: string[],
+ getUrlForApp: GetUrlForApp
+): ChromeBreadcrumb[] => {
let breadcrumb = [
{
text: i18nDetections.PAGE_TITLE,
- href: `${getDetectionEngineUrl()}${!isEmpty(search[0]) ? search[0] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: !isEmpty(search[0]) ? search[0] : '',
+ }),
},
];
- const tabBreadcrumb = getTabBreadcrumb(params.pathName, search);
+ const tabBreadcrumb = getTabBreadcrumb(params.pathName, search, getUrlForApp);
if (tabBreadcrumb) {
breadcrumb = [...breadcrumb, tabBreadcrumb];
@@ -63,7 +75,9 @@ export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeB
...breadcrumb,
{
text: params.state.ruleName,
- href: `${getRuleDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getRuleDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''),
+ }),
},
];
}
@@ -73,7 +87,9 @@ export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeB
...breadcrumb,
{
text: i18nRules.ADD_PAGE_TITLE,
- href: `${getCreateRuleUrl()}${!isEmpty(search[1]) ? search[1] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getCreateRuleUrl(!isEmpty(search[0]) ? search[0] : ''),
+ }),
},
];
}
@@ -83,7 +99,9 @@ export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeB
...breadcrumb,
{
text: i18nRules.EDIT_PAGE_TITLE,
- href: `${getEditRuleUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getEditRuleUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''),
+ }),
},
];
}
diff --git a/x-pack/plugins/security_solution/public/alerts/routes.tsx b/x-pack/plugins/security_solution/public/alerts/routes.tsx
index 897ba3269546..8f542d1f8867 100644
--- a/x-pack/plugins/security_solution/public/alerts/routes.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/routes.tsx
@@ -5,16 +5,19 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { DetectionEngineContainer } from './pages/detection_engine';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getAlertsRoutes = () => [
- (
-
- )}
- />,
-];
+export const AlertsRoutes: React.FC = () => (
+
+ (
+
+ )}
+ />
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx
index b5696c889d16..147efb9e0d2e 100644
--- a/x-pack/plugins/security_solution/public/app/app.tsx
+++ b/x-pack/plugins/security_solution/public/app/app.tsx
@@ -4,90 +4,38 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { createHashHistory, History } from 'history';
+import { History } from 'history';
import React, { memo, useMemo, FC } from 'react';
import { ApolloProvider } from 'react-apollo';
-import { Store } from 'redux';
+import { Store, Action } from 'redux';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { EuiErrorBoundary } from '@elastic/eui';
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
-import { BehaviorSubject } from 'rxjs';
-import { pluck } from 'rxjs/operators';
-import { KibanaContextProvider, useKibana, useUiSetting$ } from '../common/lib/kibana';
-import { Storage } from '../../../../../src/plugins/kibana_utils/public';
-
-import { DEFAULT_DARK_MODE } from '../../common/constants';
+import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants';
import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher';
-import { compose } from '../common/lib/compose/kibana_compose';
-import { AppFrontendLibs, AppApolloClient } from '../common/lib/lib';
-import { StartServices } from '../types';
-import { PageRouter } from './routes';
-import { createStore, createInitialState } from '../common/store';
-import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters';
import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_capabilities_provider';
-import { ManageGlobalTimeline } from '../timelines/components/manage_timeline';
+import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters';
+import { AppFrontendLibs } from '../common/lib/lib';
+import { KibanaContextProvider, useKibana, useUiSetting$ } from '../common/lib/kibana';
+import { State } from '../common/store';
import { ApolloClientContext } from '../common/utils/apollo_context';
-import { SecuritySubPlugins } from './types';
-
-interface AppPluginRootComponentProps {
- apolloClient: AppApolloClient;
- history: History;
- store: Store;
- subPluginRoutes: React.ReactElement[];
- theme: any; // eslint-disable-line @typescript-eslint/no-explicit-any
-}
-
-const AppPluginRootComponent: React.FC = ({
- apolloClient,
- theme,
- store,
- subPluginRoutes,
- history,
-}) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
-
-const AppPluginRoot = memo(AppPluginRootComponent);
+import { ManageGlobalTimeline } from '../timelines/components/manage_timeline';
+import { StartServices } from '../types';
+import { PageRouter } from './routes';
interface StartAppComponent extends AppFrontendLibs {
- subPlugins: SecuritySubPlugins;
- storage: Storage;
+ children: React.ReactNode;
+ history: History;
+ store: Store;
}
-const StartAppComponent: FC = ({ subPlugins, storage, ...libs }) => {
- const { routes: subPluginRoutes, store: subPluginsStore } = subPlugins;
+const StartAppComponent: FC = ({ children, apolloClient, history, store }) => {
const { i18n } = useKibana().services;
- const history = createHashHistory();
- const libs$ = new BehaviorSubject(libs);
-
- const store = createStore(
- createInitialState(subPluginsStore.initialState),
- subPluginsStore.reducer,
- libs$.pipe(pluck('apolloClient')),
- storage,
- subPluginsStore.middlewares
- );
const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE);
const theme = useMemo(
@@ -101,13 +49,23 @@ const StartAppComponent: FC = ({ subPlugins, storage, ...libs
return (
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
);
@@ -115,22 +73,30 @@ const StartAppComponent: FC = ({ subPlugins, storage, ...libs
const StartApp = memo(StartAppComponent);
-interface SiemAppComponentProps {
+interface SecurityAppComponentProps extends AppFrontendLibs {
+ children: React.ReactNode;
+ history: History;
services: StartServices;
- subPlugins: SecuritySubPlugins;
+ store: Store;
}
-const SiemAppComponent: React.FC = ({ services, subPlugins }) => {
- return (
-
-
-
- );
-};
+const SecurityAppComponent: React.FC = ({
+ children,
+ apolloClient,
+ history,
+ services,
+ store,
+}) => (
+
+
+ {children}
+
+
+);
-export const SiemApp = memo(SiemAppComponent);
+export const SecurityApp = memo(SecurityAppComponent);
diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx
index bb9e99326182..88e9d4179a97 100644
--- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx
+++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx
@@ -4,66 +4,68 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- getDetectionEngineUrl,
- getOverviewUrl,
- getNetworkUrl,
- getTimelinesUrl,
- getHostsUrl,
- getCaseUrl,
-} from '../../common/components/link_to';
import * as i18n from './translations';
-import { SiemPageName, SiemNavTab } from '../types';
-import { getManagementUrl } from '../../management';
+import { SecurityPageName } from '../types';
+import { SiemNavTab } from '../../common/components/navigation/types';
+import {
+ APP_OVERVIEW_PATH,
+ APP_ALERTS_PATH,
+ APP_HOSTS_PATH,
+ APP_NETWORK_PATH,
+ APP_TIMELINES_PATH,
+ APP_CASES_PATH,
+ APP_MANAGEMENT_PATH,
+} from '../../../common/constants';
export const navTabs: SiemNavTab = {
- [SiemPageName.overview]: {
- id: SiemPageName.overview,
+ [SecurityPageName.overview]: {
+ id: SecurityPageName.overview,
name: i18n.OVERVIEW,
- href: getOverviewUrl(),
+ href: APP_OVERVIEW_PATH,
disabled: false,
urlKey: 'overview',
},
- [SiemPageName.hosts]: {
- id: SiemPageName.hosts,
+ [SecurityPageName.alerts]: {
+ id: SecurityPageName.alerts,
+ name: i18n.Alerts,
+ href: APP_ALERTS_PATH,
+ disabled: false,
+ urlKey: 'alerts',
+ },
+ [SecurityPageName.hosts]: {
+ id: SecurityPageName.hosts,
name: i18n.HOSTS,
- href: getHostsUrl(),
+ href: APP_HOSTS_PATH,
disabled: false,
urlKey: 'host',
},
- [SiemPageName.network]: {
- id: SiemPageName.network,
+ [SecurityPageName.network]: {
+ id: SecurityPageName.network,
name: i18n.NETWORK,
- href: getNetworkUrl(),
+ href: APP_NETWORK_PATH,
disabled: false,
urlKey: 'network',
},
- [SiemPageName.detections]: {
- id: SiemPageName.detections,
- name: i18n.DETECTION_ENGINE,
- href: getDetectionEngineUrl(),
- disabled: false,
- urlKey: 'detections',
- },
- [SiemPageName.timelines]: {
- id: SiemPageName.timelines,
+
+ [SecurityPageName.timelines]: {
+ id: SecurityPageName.timelines,
name: i18n.TIMELINES,
- href: getTimelinesUrl(),
+ href: APP_TIMELINES_PATH,
disabled: false,
urlKey: 'timeline',
},
- [SiemPageName.case]: {
- id: SiemPageName.case,
+ [SecurityPageName.case]: {
+ id: SecurityPageName.case,
name: i18n.CASE,
- href: getCaseUrl(null),
+ href: APP_CASES_PATH,
disabled: false,
urlKey: 'case',
},
- [SiemPageName.management]: {
- id: SiemPageName.management,
+ [SecurityPageName.management]: {
+ id: SecurityPageName.management,
name: i18n.MANAGEMENT,
- href: getManagementUrl({ name: 'default' }),
+ href: APP_MANAGEMENT_PATH,
disabled: false,
- urlKey: SiemPageName.management,
+ urlKey: SecurityPageName.management,
},
};
diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx
index b5aae0b28c87..d8bdbd6e7ef5 100644
--- a/x-pack/plugins/security_solution/public/app/home/index.tsx
+++ b/x-pack/plugins/security_solution/public/app/home/index.tsx
@@ -5,7 +5,6 @@
*/
import React, { useMemo } from 'react';
-import { Redirect, Route, Switch } from 'react-router-dom';
import styled from 'styled-components';
import { useThrottledResizeObserver } from '../../common/components/utils';
@@ -13,20 +12,14 @@ import { DragDropContextWrapper } from '../../common/components/drag_and_drop/dr
import { Flyout } from '../../timelines/components/flyout';
import { HeaderGlobal } from '../../common/components/header_global';
import { HelpMenu } from '../../common/components/help_menu';
-import { LinkToPage } from '../../common/components/link_to';
-import { MlHostConditionalContainer } from '../../common/components/ml/conditional_links/ml_host_conditional_container';
-import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container';
import { AutoSaveWarningMsg } from '../../timelines/components/timeline/auto_save_warning';
import { UseUrlState } from '../../common/components/url_state';
import {
WithSource,
indicesExistOrDataTemporarilyUnavailable,
} from '../../common/containers/source';
-import { SpyRoute } from '../../common/utils/route/spy_routes';
import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline';
-import { NotFoundPage } from '../404';
import { navTabs } from './home_navigations';
-import { SiemPageName } from '../types';
const WrappedByAutoSizer = styled.div`
height: 100%;
@@ -52,10 +45,10 @@ const calculateFlyoutHeight = ({
}): number => Math.max(0, windowHeight - globalHeaderSize);
interface HomePageProps {
- subPlugins: JSX.Element[];
+ children: React.ReactNode;
}
-export const HomePage: React.FC = ({ subPlugins }) => {
+export const HomePage: React.FC = ({ children }) => {
const { ref: measureRef, height: windowHeight = 0 } = useThrottledResizeObserver();
const flyoutHeight = useMemo(
() =>
@@ -88,32 +81,13 @@ export const HomePage: React.FC = ({ subPlugins }) => {
>
)}
-
-
- {subPlugins}
- } />
- (
-
- )}
- />
- (
-
- )}
- />
- } />
-
+ {children}
)}
-
-
);
};
diff --git a/x-pack/plugins/security_solution/public/app/home/translations.ts b/x-pack/plugins/security_solution/public/app/home/translations.ts
index ccf927eba20c..f5a08e6395f1 100644
--- a/x-pack/plugins/security_solution/public/app/home/translations.ts
+++ b/x-pack/plugins/security_solution/public/app/home/translations.ts
@@ -25,6 +25,10 @@ export const DETECTION_ENGINE = i18n.translate(
}
);
+export const Alerts = i18n.translate('xpack.securitySolution.navigation.alerts', {
+ defaultMessage: 'Alerts',
+});
+
export const TIMELINES = i18n.translate('xpack.securitySolution.navigation.timelines', {
defaultMessage: 'Timelines',
});
diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx
index 13d06bd1a98c..0afd945af859 100644
--- a/x-pack/plugins/security_solution/public/app/index.tsx
+++ b/x-pack/plugins/security_solution/public/app/index.tsx
@@ -5,19 +5,35 @@
*/
import React from 'react';
+import { Store, Action } from 'redux';
import { render, unmountComponentAtNode } from 'react-dom';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { AppMountParameters } from '../../../../../src/core/public';
+import { State } from '../common/store';
import { StartServices } from '../types';
-import { SiemApp } from './app';
-import { SecuritySubPlugins } from './types';
+import { SecurityApp } from './app';
+import { AppFrontendLibs } from '../common/lib/lib';
-export const renderApp = (
- services: StartServices,
- { element }: AppMountParameters,
- subPlugins: SecuritySubPlugins
-) => {
- render( , element);
+interface RenderAppProps extends AppFrontendLibs, AppMountParameters {
+ services: StartServices;
+ store: Store;
+ SubPluginRoutes: React.FC;
+}
+
+export const renderApp = ({
+ apolloClient,
+ element,
+ history,
+ services,
+ store,
+ SubPluginRoutes,
+}: RenderAppProps) => {
+ render(
+
+
+ ,
+ element
+ );
return () => unmountComponentAtNode(element);
};
diff --git a/x-pack/plugins/security_solution/public/app/routes.tsx b/x-pack/plugins/security_solution/public/app/routes.tsx
index d1395813d39f..fc0d4e1f4fa6 100644
--- a/x-pack/plugins/security_solution/public/app/routes.tsx
+++ b/x-pack/plugins/security_solution/public/app/routes.tsx
@@ -14,17 +14,17 @@ import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes';
import { RouteCapture } from '../common/components/endpoint/route_capture';
interface RouterProps {
+ children: React.ReactNode;
history: History;
- subPluginRoutes: JSX.Element[];
}
-const PageRouterComponent: FC = ({ history, subPluginRoutes }) => (
+const PageRouterComponent: FC = ({ history, children }) => (
-
+ {children}
diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts
index 7a905b35710c..4bd888e87bbd 100644
--- a/x-pack/plugins/security_solution/public/app/types.ts
+++ b/x-pack/plugins/security_solution/public/app/types.ts
@@ -14,33 +14,20 @@ import {
CombinedState,
} from 'redux';
-import { NavTab } from '../common/components/navigation/types';
import { State, SubPluginsInitReducer } from '../common/store';
import { Immutable } from '../../common/endpoint/types';
import { AppAction } from '../common/store/actions';
import { TimelineState } from '../timelines/store/timeline/types';
-export enum SiemPageName {
+export enum SecurityPageName {
+ alerts = 'alerts',
overview = 'overview',
hosts = 'hosts',
network = 'network',
- detections = 'detections',
timelines = 'timelines',
case = 'case',
management = 'management',
}
-
-export type SiemNavTabKey =
- | SiemPageName.overview
- | SiemPageName.hosts
- | SiemPageName.network
- | SiemPageName.detections
- | SiemPageName.timelines
- | SiemPageName.case
- | SiemPageName.management;
-
-export type SiemNavTab = Record;
-
export interface SecuritySubPluginStore {
initialState: Record;
reducer: Record>;
@@ -48,7 +35,7 @@ export interface SecuritySubPluginStore
}
export interface SecuritySubPlugin {
- routes: React.ReactElement[];
+ SubPluginRoutes: React.FC;
storageTimelines?: Pick;
}
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx
index bbb96f433d3c..ed8ec432f7df 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx
@@ -29,6 +29,16 @@ const useGetCasesMock = useGetCases as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;
+jest.mock('react-router-dom', () => {
+ const originalModule = jest.requireActual('react-router-dom');
+ return {
+ ...originalModule,
+ useHistory: jest.fn(),
+ };
+});
+
+jest.mock('../../../common/components/link_to');
+
describe('AllCases', () => {
const dispatchResetIsDeleted = jest.fn();
const dispatchResetIsUpdated = jest.fn();
@@ -98,7 +108,7 @@ describe('AllCases', () => {
);
expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual(
- `#/link-to/case/${useGetCasesMockState.data.cases[0].id}?timerange=(global:(linkTo:!(timeline),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)),timeline:(linkTo:!(global),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)))`
+ `/${useGetCasesMockState.data.cases[0].id}`
);
expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().text()).toEqual(
useGetCasesMockState.data.cases[0].title
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
index d27f383fb94e..2de957039efe 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
@@ -5,9 +5,9 @@
*/
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useHistory } from 'react-router-dom';
import {
EuiBasicTable,
- EuiButton,
EuiContextMenuPanel,
EuiEmptyPrompt,
EuiFlexGroup,
@@ -27,7 +27,6 @@ import { useGetCases, UpdateCase } from '../../containers/use_get_cases';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { useDeleteCases } from '../../containers/use_delete_cases';
import { EuiBasicTableOnChange } from '../../../alerts/pages/detection_engine/rules/types';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
import { Panel } from '../../../common/components/panel';
import {
UtilityBar,
@@ -36,13 +35,11 @@ import {
UtilityBarSection,
UtilityBarText,
} from '../../../common/components/utility_bar';
-import { getCreateCaseUrl } from '../../../common/components/link_to';
+import { getCreateCaseUrl, useFormatUrl } from '../../../common/components/link_to';
import { getBulkItems } from '../bulk_actions';
import { CaseHeaderPage } from '../case_header_page';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
import { OpenClosedStats } from '../open_closed_stats';
-import { navTabs } from '../../../app/home/home_navigations';
-
import { getActions } from './actions';
import { CasesTableFilters } from './table_filters';
import { useUpdateCases } from '../../containers/use_bulk_update_case';
@@ -51,6 +48,8 @@ import { getActionLicenseError } from '../use_push_to_service/helpers';
import { CaseCallOut } from '../callout';
import { ConfigureCaseButton } from '../configure_cases/button';
import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations';
+import { LinkButton } from '../../../common/components/links';
+import { SecurityPageName } from '../../../app/types';
const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
@@ -88,7 +87,8 @@ interface AllCasesProps {
}
export const AllCases = React.memo(
({ onRowClick = () => {}, isModal = false, userCanCrud }) => {
- const urlSearch = useGetUrlSearch(navTabs.case);
+ const history = useHistory();
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
const { actionLicense } = useGetActionLicense();
const {
countClosedCases,
@@ -231,6 +231,14 @@ export const AllCases = React.memo(
[dispatchUpdateCaseProperty, fetchCasesStatus]
);
+ const goToCreateCase = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getCreateCaseUrl(urlSearch));
+ },
+ [history, urlSearch]
+ );
+
const actions = useMemo(
() =>
getActions({
@@ -342,15 +350,16 @@ export const AllCases = React.memo(
/>
-
{i18n.CREATE_TITLE}
-
+
@@ -418,15 +427,16 @@ export const AllCases = React.memo(
titleSize="xs"
body={i18n.NO_CASES_BODY}
actions={
-
{i18n.ADD_NEW_CASE}
-
+
}
/>
}
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
index a24cb6a87de7..67b0f5636746 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
@@ -15,6 +15,15 @@ import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { useUpdateCases } from '../../containers/use_bulk_update_case';
import { EuiTableRow } from '@elastic/eui';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../common/components/link_to');
+
jest.mock('../../containers/use_bulk_update_case');
jest.mock('../../containers/use_delete_cases');
jest.mock('../../containers/use_get_cases');
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
index 2641ac68cf95..1721cb5f819f 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
@@ -15,6 +15,13 @@ import * as i18n from './translations';
jest.mock('../../containers/use_delete_cases');
const useDeleteCasesMock = useDeleteCases as jest.Mock;
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
describe('CaseView actions', () => {
const handleOnDeleteConfirm = jest.fn();
const handleToggleModal = jest.fn();
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx
index df1082ec48f9..6439972ccbb5 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.tsx
@@ -6,11 +6,10 @@
import { isEmpty } from 'lodash/fp';
import React, { useMemo } from 'react';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import * as i18n from './translations';
import { useDeleteCases } from '../../containers/use_delete_cases';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
-import { SiemPageName } from '../../../app/types';
import { PropertyActions } from '../property_actions';
import { Case } from '../../containers/types';
import { CaseService } from '../../containers/use_get_case_user_actions';
@@ -26,6 +25,7 @@ const CaseViewActionsComponent: React.FC = ({
currentExternalIncident,
disabled = false,
}) => {
+ const history = useHistory();
// Delete case
const {
handleToggleModal,
@@ -69,7 +69,8 @@ const CaseViewActionsComponent: React.FC = ({
);
if (isDeleted) {
- return ;
+ history.push('/');
+ return null;
}
return (
<>
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
index bb63cf633747..3718249479b6 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
@@ -37,6 +37,7 @@ import { useGetCaseUserActions } from '../../containers/use_get_case_user_action
import { usePushToService } from '../use_push_to_service';
import { EditConnector } from '../edit_connector';
import { useConnectors } from '../../containers/configure/use_connectors';
+import { SecurityPageName } from '../../../app/types';
interface Props {
caseId: string;
@@ -70,7 +71,7 @@ export interface CaseProps extends Props {
export const CaseComponent = React.memo(
({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => {
const basePath = window.location.origin + useBasePath();
- const caseLink = `${basePath}/app/security#/case/${caseId}`;
+ const caseLink = `${basePath}/app/security/cases/${caseId}`;
const search = useGetUrlSearch(navTabs.case);
const [initLoadingData, setInitLoadingData] = useState(true);
const {
@@ -252,6 +253,7 @@ export const CaseComponent = React.memo(
href: getCaseUrl(search),
text: i18n.BACK_TO_ALL,
dataTestSubj: 'backToCases',
+ pageId: SecurityPageName.case,
}),
[search]
);
@@ -356,7 +358,7 @@ export const CaseComponent = React.memo(
-
+
>
);
}
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
index aefe515f4bd4..1315cf1c962e 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
@@ -12,6 +12,15 @@ import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button';
import { TestProviders } from '../../../common/mock';
import { searchURL } from './__mock__';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../common/components/link_to');
+
describe('Configuration button', () => {
let wrapper: ReactWrapper;
const props: ConfigureCaseButtonProps = {
@@ -35,7 +44,7 @@ describe('Configuration button', () => {
test('it pass the correct props to the button', () => {
expect(wrapper.find('[data-test-subj="configure-case-button"]').first().props()).toMatchObject({
- href: `#/link-to/case/configure${searchURL}`,
+ href: `/configure`,
iconType: 'controlsHorizontal',
isDisabled: false,
'aria-label': 'My label',
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx
index a6d78d4a2a62..44767471dd9e 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx
@@ -4,9 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButton, EuiToolTip } from '@elastic/eui';
-import React, { memo, useMemo } from 'react';
-import { getConfigureCasesUrl } from '../../../common/components/link_to';
+import { EuiToolTip } from '@elastic/eui';
+import React, { memo, useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+
+import { getConfigureCasesUrl, useFormatUrl } from '../../../common/components/link_to';
+import { LinkButton } from '../../../common/components/links';
+import { SecurityPageName } from '../../../app/types';
export interface ConfigureCaseButtonProps {
label: string;
@@ -25,19 +29,29 @@ const ConfigureCaseButtonComponent: React.FC = ({
titleTooltip,
urlSearch,
}: ConfigureCaseButtonProps) => {
+ const history = useHistory();
+ const { formatUrl } = useFormatUrl(SecurityPageName.case);
+ const goToCaseConfigure = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getConfigureCasesUrl(urlSearch));
+ },
+ [history, urlSearch]
+ );
const configureCaseButton = useMemo(
() => (
-
{label}
-
+
),
- [label, isDisabled, urlSearch]
+ [label, isDisabled, formatUrl, goToCaseConfigure]
);
return showToolTip ? (
{
);
wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');
- expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(`/${SiemPageName.case}`);
+ expect(mockHistory.push).toHaveBeenCalledWith('/');
});
it('should redirect to new case when caseData is there', () => {
const sampleId = '777777';
@@ -122,9 +122,7 @@ describe('Create case', () => {
);
- expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(
- `/${SiemPageName.case}/${sampleId}`
- );
+ expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/777777');
});
it('should render spinner when loading', () => {
diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx
index 1b65b2582c76..9f078c725c3c 100644
--- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx
@@ -13,7 +13,7 @@ import {
EuiPanel,
} from '@elastic/eui';
import styled, { css } from 'styled-components';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import { isEqual } from 'lodash/fp';
import { CasePostRequest } from '../../../../../case/common/api';
@@ -30,9 +30,9 @@ import { schema } from './schema';
import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import * as i18n from '../../translations';
-import { SiemPageName } from '../../../app/types';
import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form';
import { useGetTags } from '../../containers/use_get_tags';
+import { getCaseDetailsUrl } from '../../../common/components/link_to';
export const CommonUseField = getUseField({ component: Field });
@@ -61,8 +61,8 @@ const initialCaseValue: CasePostRequest = {
};
export const Create = React.memo(() => {
+ const history = useHistory();
const { caseData, isLoading, postCase } = usePostCase();
- const [isCancel, setIsCancel] = useState(false);
const { form } = useForm({
defaultValue: initialCaseValue,
options: { stripEmptyFields: false },
@@ -98,15 +98,12 @@ export const Create = React.memo(() => {
}, [form]);
const handleSetIsCancel = useCallback(() => {
- setIsCancel(true);
- }, []);
+ history.push('/');
+ }, [history]);
if (caseData != null && caseData.id) {
- return ;
- }
-
- if (isCancel) {
- return ;
+ history.push(getCaseDetailsUrl({ id: caseData.id }));
+ return null;
}
return (
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
index 4391db1a0a0a..9dfeec3b26ba 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
@@ -14,6 +14,15 @@ import * as i18n from './translations';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { getKibanaConfigError, getLicenseError } from './helpers';
import { connectorsMock } from '../../containers/configure/mock';
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../common/components/link_to');
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/use_post_push_to_service');
jest.mock('../../containers/configure/api');
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx
index f18870787ded..45b515ccacac 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx
@@ -4,21 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButton, EuiLink, EuiToolTip } from '@elastic/eui';
+import { EuiButton, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
import { Case } from '../../containers/types';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
-import { getConfigureCasesUrl } from '../../../common/components/link_to';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
+import { getConfigureCasesUrl, useFormatUrl } from '../../../common/components/link_to';
import { CaseCallOut } from '../callout';
import { getLicenseError, getKibanaConfigError } from './helpers';
import * as i18n from './translations';
import { Connector } from '../../../../../case/common/api/cases';
import { CaseServices } from '../../containers/use_get_case_user_actions';
+import { LinkAnchor } from '../../../common/components/links';
+import { SecurityPageName } from '../../../app/types';
export interface UsePushToService {
caseId: string;
@@ -48,8 +49,8 @@ export const usePushToService = ({
userCanCrud,
isValidConnector,
}: UsePushToService): ReturnUsePushToService => {
- const urlSearch = useGetUrlSearch(navTabs.case);
-
+ const history = useHistory();
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
const { isLoading, postPushToService } = usePostPushToService();
const { isLoading: loadingLicense, actionLicense } = useGetActionLicense();
@@ -66,6 +67,14 @@ export const usePushToService = ({
}
}, [caseId, caseServices, caseConnectorId, caseConnectorName, postPushToService, updateCase]);
+ const goToConfigureCases = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getConfigureCasesUrl(urlSearch));
+ },
+ [history, urlSearch]
+ );
+
const errorsMsg = useMemo(() => {
let errors: Array<{
title: string;
@@ -86,9 +95,13 @@ export const usePushToService = ({
id="xpack.securitySolution.case.caseView.pushToServiceDisableByNoConnectors"
values={{
link: (
-
+
{i18n.LINK_CONNECTOR_CONFIGURE}
-
+
),
}}
/>
diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
index ae9f1ec7469e..b2d8ce9c891b 100644
--- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
@@ -18,7 +18,7 @@ const onSaveContent = jest.fn();
const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c';
const defaultProps = {
- content: `A link to a timeline [timeline](http://localhost:5601/app/security#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`,
+ content: `A link to a timeline [timeline](http://localhost:5601/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t))`,
id: 'markdown-id',
isEditable: false,
onChangeEditable,
diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx
index 307790194421..0bd6100b05df 100644
--- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_title.tsx
@@ -23,7 +23,7 @@ import { LocalizedDateTooltip } from '../../../common/components/localized_date_
import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
import { navTabs } from '../../../app/home/home_navigations';
import { PropertyActions } from '../property_actions';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
import * as i18n from './translations';
const MySpinner = styled(EuiLoadingSpinner)`
@@ -94,7 +94,7 @@ export const UserActionTitle = ({
const handleAnchorLink = useCallback(() => {
copy(
- `${window.location.origin}${window.location.pathname}#${SiemPageName.case}/${caseId}/${id}${urlSearch}`
+ `${window.location.origin}${window.location.pathname}#${SecurityPageName.case}/${caseId}/${id}${urlSearch}`
);
}, [caseId, id, urlSearch]);
diff --git a/x-pack/plugins/security_solution/public/cases/index.ts b/x-pack/plugins/security_solution/public/cases/index.ts
index 1eb8c82532e2..3b3130152f38 100644
--- a/x-pack/plugins/security_solution/public/cases/index.ts
+++ b/x-pack/plugins/security_solution/public/cases/index.ts
@@ -5,14 +5,14 @@
*/
import { SecuritySubPlugin } from '../app/types';
-import { getCasesRoutes } from './routes';
+import { CasesRoutes } from './routes';
export class Cases {
public setup() {}
public start(): SecuritySubPlugin {
return {
- routes: getCasesRoutes(),
+ SubPluginRoutes: CasesRoutes,
};
}
}
diff --git a/x-pack/plugins/security_solution/public/cases/pages/case.tsx b/x-pack/plugins/security_solution/public/cases/pages/case.tsx
index 03ebec34c2cd..eb6da9579e4e 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/case.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/case.tsx
@@ -13,6 +13,7 @@ import { AllCases } from '../components/all_cases';
import { savedObjectReadOnly, CaseCallOut } from '../components/callout';
import { CaseSavedObjectNoPermissions } from './saved_object_no_permissions';
+import { SecurityPageName } from '../../app/types';
export const CasesPage = React.memo(() => {
const userPermissions = useGetUserSavedObjectPermissions();
@@ -28,7 +29,7 @@ export const CasesPage = React.memo(() => {
)}
-
+
>
) : (
diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx
index 780de303c02d..43c51b32bce0 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx
@@ -5,8 +5,10 @@
*/
import React from 'react';
-import { useParams, Redirect } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
+import { SecurityPageName } from '../../app/types';
+import { SpyRoute } from '../../common/utils/route/spy_routes';
import { WrapperPage } from '../../common/components/wrapper_page';
import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search';
import { useGetUserSavedObjectPermissions } from '../../common/lib/kibana';
@@ -16,12 +18,14 @@ import { CaseView } from '../components/case_view';
import { savedObjectReadOnly, CaseCallOut } from '../components/callout';
export const CaseDetailsPage = React.memo(() => {
+ const history = useHistory();
const userPermissions = useGetUserSavedObjectPermissions();
const { detailName: caseId } = useParams();
const search = useGetUrlSearch(navTabs.case);
if (userPermissions != null && !userPermissions.read) {
- return ;
+ history.replace(getCaseUrl(search));
+ return null;
}
return caseId != null ? (
@@ -35,6 +39,7 @@ export const CaseDetailsPage = React.memo(() => {
)}
+
>
) : null;
});
diff --git a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx
index f70ff859e8e7..83354dabca1f 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx
@@ -5,9 +5,10 @@
*/
import React, { useMemo } from 'react';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
+import { SecurityPageName } from '../../app/types';
import { getCaseUrl } from '../../common/components/link_to';
import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search';
import { WrapperPage } from '../../common/components/wrapper_page';
@@ -20,6 +21,7 @@ import { WhitePageWrapper, SectionWrapper } from '../components/wrappers';
import * as i18n from './translations';
const ConfigureCasesPageComponent: React.FC = () => {
+ const history = useHistory();
const userPermissions = useGetUserSavedObjectPermissions();
const search = useGetUrlSearch(navTabs.case);
@@ -27,12 +29,14 @@ const ConfigureCasesPageComponent: React.FC = () => {
() => ({
href: getCaseUrl(search),
text: i18n.BACK_TO_ALL,
+ pageId: SecurityPageName.case,
}),
[search]
);
if (userPermissions != null && !userPermissions.read) {
- return ;
+ history.push(getCaseUrl(search));
+ return null;
}
const HeaderWrapper = styled.div`
@@ -51,7 +55,7 @@ const ConfigureCasesPageComponent: React.FC = () => {
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx
index c586a90e5ef9..672f44bfe275 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx
@@ -5,8 +5,9 @@
*/
import React, { useMemo } from 'react';
-import { Redirect } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
+import { SecurityPageName } from '../../app/types';
import { getCaseUrl } from '../../common/components/link_to';
import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search';
import { WrapperPage } from '../../common/components/wrapper_page';
@@ -18,6 +19,7 @@ import { Create } from '../components/create';
import * as i18n from './translations';
export const CreateCasePage = React.memo(() => {
+ const history = useHistory();
const userPermissions = useGetUserSavedObjectPermissions();
const search = useGetUrlSearch(navTabs.case);
@@ -25,12 +27,14 @@ export const CreateCasePage = React.memo(() => {
() => ({
href: getCaseUrl(search),
text: i18n.BACK_TO_ALL,
+ pageId: SecurityPageName.case,
}),
[search]
);
if (userPermissions != null && !userPermissions.crud) {
- return ;
+ history.replace(getCaseUrl(search));
+ return null;
}
return (
@@ -39,7 +43,7 @@ export const CreateCasePage = React.memo(() => {
-
+
>
);
});
diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
index 32f64d2690cb..814be240644f 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
@@ -7,13 +7,12 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
-import { SiemPageName } from '../../app/types';
import { CaseDetailsPage } from './case_details';
import { CasesPage } from './case';
import { CreateCasePage } from './create_case';
import { ConfigureCasesPage } from './configure_cases';
-const casesPagePath = `/:pageName(${SiemPageName.case})`;
+const casesPagePath = '';
const caseDetailsPagePath = `${casesPagePath}/:detailName`;
const caseDetailsPagePathWithCommentId = `${casesPagePath}/:detailName/:commentId`;
const createCasePagePath = `${casesPagePath}/create`;
@@ -21,21 +20,21 @@ const configureCasesPagePath = `${casesPagePath}/configure`;
const CaseContainerComponent: React.FC = () => (
-
-
-
-
+
-
+
-
+
-
+
+
+
+
);
diff --git a/x-pack/plugins/security_solution/public/cases/pages/utils.ts b/x-pack/plugins/security_solution/public/cases/pages/utils.ts
index 0b60d66756d0..76308e6a1dd9 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/utils.ts
+++ b/x-pack/plugins/security_solution/public/cases/pages/utils.ts
@@ -8,17 +8,26 @@ import { isEmpty } from 'lodash/fp';
import { ChromeBreadcrumb } from 'src/core/public';
-import { getCaseDetailsUrl, getCaseUrl, getCreateCaseUrl } from '../../common/components/link_to';
+import { getCaseDetailsUrl, getCreateCaseUrl } from '../../common/components/link_to';
import { RouteSpyState } from '../../common/utils/route/types';
import * as i18n from './translations';
+import { GetUrlForApp } from '../../common/components/navigation/types';
+import { APP_ID } from '../../../common/constants';
+import { SecurityPageName } from '../../app/types';
-export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeBreadcrumb[] => {
- const queryParameters = !isEmpty(search[0]) ? search[0] : null;
+export const getBreadcrumbs = (
+ params: RouteSpyState,
+ search: string[],
+ getUrlForApp: GetUrlForApp
+): ChromeBreadcrumb[] => {
+ const queryParameters = !isEmpty(search[0]) ? search[0] : '';
let breadcrumb = [
{
text: i18n.PAGE_TITLE,
- href: getCaseUrl(queryParameters),
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: queryParameters,
+ }),
},
];
if (params.detailName === 'create') {
@@ -26,7 +35,9 @@ export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeB
...breadcrumb,
{
text: i18n.CREATE_BC_TITLE,
- href: getCreateCaseUrl(queryParameters),
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCreateCaseUrl(queryParameters),
+ }),
},
];
} else if (params.detailName != null) {
@@ -34,7 +45,9 @@ export const getBreadcrumbs = (params: RouteSpyState, search: string[]): ChromeB
...breadcrumb,
{
text: params.state?.caseTitle ?? '',
- href: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }),
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }),
+ }),
},
];
}
diff --git a/x-pack/plugins/security_solution/public/cases/routes.tsx b/x-pack/plugins/security_solution/public/cases/routes.tsx
index 698350e49bc3..c321f83c693f 100644
--- a/x-pack/plugins/security_solution/public/cases/routes.tsx
+++ b/x-pack/plugins/security_solution/public/cases/routes.tsx
@@ -5,13 +5,16 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { Case } from './pages';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getCasesRoutes = () => [
-
-
- ,
-];
+export const CasesRoutes: React.FC = () => (
+
+
+
+
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
index aa5efe3ccfe6..e60d876617dc 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
@@ -22,6 +22,8 @@ import {
timelineDefaults,
} from '../../../timelines/components/manage_timeline';
+jest.mock('../link_to');
+
jest.mock('../../lib/kibana');
jest.mock('uuid', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
index cb2cbcc62aba..30c7d2a742b5 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
@@ -15,6 +15,8 @@ import { mockBrowserFields } from '../../containers/source/mock';
import { defaultHeaders } from '../../mock/header';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../link_to');
+
describe('EventDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx
index 9f60d16a6f67..c0cc1c04488b 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx
@@ -14,6 +14,8 @@ import { mockBrowserFields } from '../../containers/source/mock';
import { defaultHeaders } from '../../mock/header';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../link_to');
+
describe('EventFieldsBrowser', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
index 9f1d71108e2f..1645db371802 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
@@ -190,6 +190,7 @@ export const StatefulEventsViewer = connector(
(prevProps, nextProps) =>
prevProps.id === nextProps.id &&
deepEqual(prevProps.columns, nextProps.columns) &&
+ deepEqual(prevProps.defaultIndices, nextProps.defaultIndices) &&
deepEqual(prevProps.dataProviders, nextProps.dataProviders) &&
prevProps.deletedEventIds === nextProps.deletedEventIds &&
prevProps.end === nextProps.end &&
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx
index 79ed8bb37949..68a1e7220979 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx
@@ -243,7 +243,7 @@ const ExceptionsViewerComponent = ({
// Used in utility bar info text
const ruleSettingsUrl = useMemo((): string => {
return services.application.getUrlForApp(
- `security#/detections/rules/id/${encodeURI(ruleId)}/edit`
+ `security/detections/rules/id/${encodeURI(ruleId)}/edit`
);
}, [ruleId, services.application]);
diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_global/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 25374c63fa89..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/header_global/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`HeaderGlobal it renders 1`] = `
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx
deleted file mode 100644
index 809f0eeb811f..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx
+++ /dev/null
@@ -1,40 +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 { shallow } from 'enzyme';
-import React from 'react';
-
-import '../../mock/match_media';
-import { HeaderGlobal } from './index';
-
-jest.mock('react-router-dom', () => ({
- useLocation: () => ({
- pathname: '/app/siem#/hosts/allHosts',
- hash: '',
- search: '',
- state: '',
- }),
- withRouter: () => jest.fn(),
- generatePath: jest.fn(),
-}));
-
-// Test will fail because we will to need to mock some core services to make the test work
-// For now let's forget about SiemSearchBar
-jest.mock('../search_bar', () => ({
- SiemSearchBar: () => null,
-}));
-
-describe('HeaderGlobal', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
-
- test('it renders', () => {
- const wrapper = shallow( );
-
- expect(wrapper).toMatchSnapshot();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
index c9085b595381..de19c1903586 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
@@ -4,21 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui';
+import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import { pickBy } from 'lodash/fp';
-import React from 'react';
+import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';
-import { useLocation } from 'react-router-dom';
import { gutterTimeline } from '../../lib/helpers';
import { navTabs } from '../../../app/home/home_navigations';
-import { SiemPageName } from '../../../app/types';
-import { getOverviewUrl } from '../link_to';
+import { SecurityPageName } from '../../../app/types';
+import { getAppOverviewUrl } from '../link_to';
import { MlPopover } from '../ml_popover/ml_popover';
import { SiemNavigation } from '../navigation';
import * as i18n from './translations';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
-import { ADD_DATA_PATH } from '../../../../common/constants';
+import { useGetUrlSearch } from '../navigation/use_get_url_search';
+import { useKibana } from '../../lib/kibana';
+import { APP_ID, ADD_DATA_PATH, APP_ALERTS_PATH } from '../../../../common/constants';
+import { LinkAnchor } from '../links';
const Wrapper = styled.header`
${({ theme }) => css`
@@ -39,7 +41,15 @@ interface HeaderGlobalProps {
hideDetectionEngine?: boolean;
}
export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => {
- const currentLocation = useLocation();
+ const search = useGetUrlSearch(navTabs.overview);
+ const { navigateToApp } = useKibana().services.application;
+ const goToOverview = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { path: search });
+ },
+ [navigateToApp, search]
+ );
return (
@@ -50,9 +60,9 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
-
+
-
+
@@ -61,14 +71,14 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
display="condensed"
navTabs={
hideDetectionEngine
- ? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
+ ? pickBy((_, key) => key !== SecurityPageName.alerts, navTabs)
: navTabs
}
/>
) : (
key === SiemPageName.overview, navTabs)}
+ navTabs={pickBy((_, key) => key === SecurityPageName.overview, navTabs)}
/>
)}
@@ -78,7 +88,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
{indicesExistOrDataTemporarilyUnavailable(indicesExist) &&
- currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && (
+ window.location.pathname.includes(APP_ALERTS_PATH) && (
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
index 29f227578ed9..ef838fc50d10 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
@@ -11,6 +11,16 @@ import React from 'react';
import { TestProviders } from '../../mock';
import { HeaderPage } from './index';
import { useMountAppended } from '../../utils/use_mount_appended';
+import { SecurityPageName } from '../../../app/types';
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../link_to');
describe('HeaderPage', () => {
const mount = useMountAppended();
@@ -34,7 +44,10 @@ describe('HeaderPage', () => {
test('it renders the back link when provided', () => {
const wrapper = mount(
-
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
index 3656b326e26d..62880e7510cd 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
@@ -5,13 +5,16 @@
*/
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui';
-import React from 'react';
+import React, { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
import styled, { css } from 'styled-components';
import { LinkIcon, LinkIconProps } from '../link_icon';
import { Subtitle, SubtitleProps } from '../subtitle';
import { Title } from './title';
import { DraggableArguments, BadgeOptions, TitleProp } from './types';
+import { useFormatUrl } from '../link_to';
+import { SecurityPageName } from '../../../app/types';
interface HeaderProps {
border?: boolean;
@@ -61,6 +64,7 @@ interface BackOptions {
href: LinkIconProps['href'];
text: LinkIconProps['children'];
dataTestSubj?: string;
+ pageId: SecurityPageName;
}
export interface HeaderPageProps extends HeaderProps {
@@ -86,42 +90,56 @@ const HeaderPageComponent: React.FC = ({
title,
titleNode,
...rest
-}) => (
-
-
-
- {backOptions && (
-
-
- {backOptions.text}
-
-
- )}
-
- {titleNode || (
-
- )}
+}) => {
+ const history = useHistory();
+ const { formatUrl } = useFormatUrl(backOptions?.pageId ?? SecurityPageName.overview);
+ const goTo = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ if (backOptions) {
+ history.push(backOptions.href ?? '');
+ }
+ },
+ [backOptions, history]
+ );
+ return (
+
+
+
+ {backOptions && (
+
+
+ {backOptions.text}
+
+
+ )}
- {subtitle && }
- {subtitle2 && }
- {border && isLoading && }
-
+ {titleNode || (
+
+ )}
- {children && (
-
- {children}
+ {subtitle && }
+ {subtitle2 && }
+ {border && isLoading && }
- )}
-
-
-);
+
+ {children && (
+
+ {children}
+
+ )}
+
+
+ );
+};
export const HeaderPage = React.memo(HeaderPageComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
new file mode 100644
index 000000000000..6c9620e27fab
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SecurityPageName } from '../../../../app/types';
+
+export { getDetectionEngineUrl } from '../redirect_to_detection_engine';
+export { getAppOverviewUrl } from '../redirect_to_overview';
+export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
+export { getNetworkUrl, getIPDetailsUrl } from '../redirect_to_network';
+export { getTimelinesUrl, getTimelineTabsUrl } from '../redirect_to_timelines';
+export {
+ getCaseDetailsUrl,
+ getCaseUrl,
+ getCreateCaseUrl,
+ getConfigureCasesUrl,
+} from '../redirect_to_case';
+
+export const useFormatUrl = (page: SecurityPageName) => ({
+ formatUrl: (path: string) => path,
+ search: '',
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/link_to/helpers.test.ts
index 14b367de674a..97be9630c219 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/helpers.test.ts
@@ -14,6 +14,6 @@ describe('appendSearch', () => {
expect(appendSearch(undefined)).toEqual('');
});
test('should return parameter if parameter is defined', () => {
- expect(appendSearch('helloWorld')).toEqual('helloWorld');
+ expect(appendSearch('helloWorld')).toEqual('?helloWorld');
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/helpers.ts b/x-pack/plugins/security_solution/public/common/components/link_to/helpers.ts
index 9d818ab3b647..cf62547a0c72 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/helpers.ts
@@ -4,4 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const appendSearch = (search?: string) => (search != null ? `${search}` : '');
+import { isEmpty } from 'lodash/fp';
+
+export const appendSearch = (search?: string) =>
+ isEmpty(search) ? '' : `${search?.startsWith('?') ? search : `?${search}`}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
index c35a60766d7b..140fa0e46017 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
@@ -4,25 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { LinkToPage } from './link_to';
-export {
- getDetectionEngineUrl,
- RedirectToDetectionEnginePage,
-} from './redirect_to_detection_engine';
-export { getOverviewUrl, RedirectToOverviewPage } from './redirect_to_overview';
+import { isEmpty } from 'lodash/fp';
+import { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+import { SecurityPageName } from '../../../app/types';
+import { useGetUrlSearch } from '../navigation/use_get_url_search';
+import { navTabs } from '../../../app/home/home_navigations';
+
+export { getDetectionEngineUrl } from './redirect_to_detection_engine';
+export { getAppOverviewUrl } from './redirect_to_overview';
export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts';
-export { getNetworkUrl, getIPDetailsUrl, RedirectToNetworkPage } from './redirect_to_network';
-export {
- getTimelinesUrl,
- getTimelineTabsUrl,
- RedirectToTimelinesPage,
-} from './redirect_to_timelines';
+export { getNetworkUrl, getIPDetailsUrl } from './redirect_to_network';
+export { getTimelinesUrl, getTimelineTabsUrl } from './redirect_to_timelines';
export {
getCaseDetailsUrl,
getCaseUrl,
getCreateCaseUrl,
getConfigureCasesUrl,
- RedirectToCasePage,
- RedirectToCreatePage,
- RedirectToConfigureCasesPage,
} from './redirect_to_case';
+
+export const useFormatUrl = (page: SecurityPageName) => {
+ const history = useHistory();
+ const search = useGetUrlSearch(navTabs[page]);
+ const formatUrl = useCallback(
+ (path: string) => {
+ const pathArr = path.split('?');
+ return history.createHref({
+ pathname: pathArr[0],
+ search: isEmpty(pathArr[1])
+ ? search
+ : `${pathArr[1]}${isEmpty(search) ? '' : `&${search}`}`,
+ });
+ },
+ [history, search]
+ );
+ return { formatUrl, search };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/link_to.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/link_to.tsx
deleted file mode 100644
index 0294d175aef1..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/link_to/link_to.tsx
+++ /dev/null
@@ -1,126 +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 React from 'react';
-import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
-
-import { SiemPageName } from '../../../app/types';
-import { HostsTableType } from '../../../hosts/store/model';
-import {
- RedirectToCreateRulePage,
- RedirectToDetectionEnginePage,
- RedirectToEditRulePage,
- RedirectToRuleDetailsPage,
- RedirectToRulesPage,
-} from './redirect_to_detection_engine';
-import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_hosts';
-import { RedirectToNetworkPage } from './redirect_to_network';
-import { RedirectToOverviewPage } from './redirect_to_overview';
-import { RedirectToTimelinesPage } from './redirect_to_timelines';
-import {
- RedirectToCasePage,
- RedirectToCreatePage,
- RedirectToConfigureCasesPage,
-} from './redirect_to_case';
-import { TimelineType } from '../../../../common/types/timeline';
-import { RedirectToManagementPage } from './redirect_to_management';
-
-interface LinkToPageProps {
- match: RouteMatch<{}>;
-}
-
-export const LinkToPage = React.memo(({ match }) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-));
-
-LinkToPage.displayName = 'LinkToPage';
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx
index e0c03519c6cb..7005460999fc 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx
@@ -4,41 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
import { appendSearch } from './helpers';
-import { RedirectWrapper } from './redirect_wrapper';
-import { SiemPageName } from '../../../app/types';
-export type CaseComponentProps = RouteComponentProps<{
- detailName: string;
-}>;
+export const getCaseUrl = (search: string | null) => `${appendSearch(search ?? undefined)}`;
-export const RedirectToCasePage = ({
- match: {
- params: { detailName },
- },
-}: CaseComponentProps) => (
-
-);
+export const getCaseDetailsUrl = ({ id, search }: { id: string; search?: string | null }) =>
+ `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`;
-export const RedirectToCreatePage = () => ;
-export const RedirectToConfigureCasesPage = () => (
-
-);
+export const getCreateCaseUrl = (search?: string | null) =>
+ `/create${appendSearch(search ?? undefined)}`;
-const baseCaseUrl = `#/link-to/${SiemPageName.case}`;
-
-export const getCaseUrl = (search: string | null) =>
- `${baseCaseUrl}${appendSearch(search ?? undefined)}`;
-
-export const getCaseDetailsUrl = ({ id, search }: { id: string; search: string | null }) =>
- `${baseCaseUrl}/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`;
-
-export const getCreateCaseUrl = (search: string | null) =>
- `${baseCaseUrl}/create${appendSearch(search ?? undefined)}`;
-
-export const getConfigureCasesUrl = (search: string) =>
- `${baseCaseUrl}/configure${appendSearch(search ?? undefined)}`;
+export const getConfigureCasesUrl = (search?: string) =>
+ `/configure${appendSearch(search ?? undefined)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx
index e1a021192ab8..20e5367bfdde 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx
@@ -4,65 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-
import { appendSearch } from './helpers';
-import { RedirectWrapper } from './redirect_wrapper';
-
-export type DetectionEngineComponentProps = RouteComponentProps<{
- detailName: string;
- search: string;
-}>;
-
-export const DETECTION_ENGINE_PAGE_NAME = 'detections';
-
-export const RedirectToDetectionEnginePage = ({
- location: { search },
-}: DetectionEngineComponentProps) => {
- const to = `/${DETECTION_ENGINE_PAGE_NAME}${search}`;
-
- return ;
-};
-export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => {
- return ;
-};
+export const getDetectionEngineUrl = (search?: string) => `${appendSearch(search)}`;
-export const RedirectToCreateRulePage = ({
- location: { search },
-}: DetectionEngineComponentProps) => {
- return ;
-};
+export const getDetectionEngineTabUrl = (tabPath: string, search?: string) =>
+ `/${tabPath}${appendSearch(search)}`;
-export const RedirectToRuleDetailsPage = ({
- match: {
- params: { detailName },
- },
- location: { search },
-}: DetectionEngineComponentProps) => {
- return ;
-};
+export const getRulesUrl = (search?: string) => `/rules${appendSearch(search)}`;
-export const RedirectToEditRulePage = ({
- match: {
- params: { detailName },
- },
- location: { search },
-}: DetectionEngineComponentProps) => {
- return (
-
- );
-};
+export const getCreateRuleUrl = (search?: string) => `/rules/create${appendSearch(search)}`;
-const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`;
+export const getRuleDetailsUrl = (detailName: string, search?: string) =>
+ `/rules/id/${detailName}${appendSearch(search)}`;
-export const getDetectionEngineUrl = (search?: string) =>
- `${baseDetectionEngineUrl}${appendSearch(search)}`;
-export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`;
-export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`;
-export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`;
-export const getRuleDetailsUrl = (detailName: string) =>
- `${baseDetectionEngineUrl}/rules/id/${detailName}`;
-export const getEditRuleUrl = (detailName: string) =>
- `${baseDetectionEngineUrl}/rules/id/${detailName}/edit`;
+export const getEditRuleUrl = (detailName: string, search?: string) =>
+ `/rules/id/${detailName}/edit${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx
index 0cfe8e655e25..5af24e5f7ce6 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx
@@ -4,55 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-
import { HostsTableType } from '../../../hosts/store/model';
-import { SiemPageName } from '../../../app/types';
import { appendSearch } from './helpers';
-import { RedirectWrapper } from './redirect_wrapper';
-
-export type HostComponentProps = RouteComponentProps<{
- detailName: string;
- tabName: HostsTableType;
- search: string;
-}>;
-
-export const RedirectToHostsPage = ({
- match: {
- params: { tabName },
- },
- location: { search },
-}: HostComponentProps) => {
- const defaultSelectedTab = HostsTableType.hosts;
- const selectedTab = tabName ? tabName : defaultSelectedTab;
- const to = `/${SiemPageName.hosts}/${selectedTab}${search}`;
-
- return ;
-};
-
-export const RedirectToHostDetailsPage = ({
- match: {
- params: { detailName, tabName },
- },
- location: { search },
-}: HostComponentProps) => {
- const defaultSelectedTab = HostsTableType.authentications;
- const selectedTab = tabName ? tabName : defaultSelectedTab;
- const to = `/${SiemPageName.hosts}/${detailName}/${selectedTab}${search}`;
- return ;
-};
-
-const baseHostsUrl = `#/link-to/${SiemPageName.hosts}`;
-export const getHostsUrl = (search?: string) => `${baseHostsUrl}${appendSearch(search)}`;
+export const getHostsUrl = (search?: string) => `${appendSearch(search)}`;
export const getTabsOnHostsUrl = (tabName: HostsTableType, search?: string) =>
- `${baseHostsUrl}/${tabName}${appendSearch(search)}`;
+ `/${tabName}${appendSearch(search)}`;
-export const getHostDetailsUrl = (detailName: string) => `${baseHostsUrl}/${detailName}`;
+export const getHostDetailsUrl = (detailName: string, search?: string) =>
+ `/${detailName}${appendSearch(search)}`;
-export const getTabsOnHostDetailsUrl = (detailName: string, tabName: HostsTableType) => {
- return `${baseHostsUrl}/${detailName}/${tabName}`;
-};
+export const getTabsOnHostDetailsUrl = (
+ detailName: string,
+ tabName: HostsTableType,
+ search?: string
+) => `/${detailName}/${tabName}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_management.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_management.tsx
deleted file mode 100644
index 595c203993bb..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_management.tsx
+++ /dev/null
@@ -1,15 +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 React, { memo } from 'react';
-import { RedirectWrapper } from './redirect_wrapper';
-import { SiemPageName } from '../../../app/types';
-
-export const RedirectToManagementPage = memo(() => {
- return ;
-});
-
-RedirectToManagementPage.displayName = 'RedirectToManagementPage';
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_network.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_network.tsx
index d72bacf511fa..8e2b47bd91db 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_network.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_network.tsx
@@ -4,39 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-
-import { SiemPageName } from '../../../app/types';
import { FlowTarget, FlowTargetSourceDest } from '../../../graphql/types';
import { appendSearch } from './helpers';
-import { RedirectWrapper } from './redirect_wrapper';
-
-export type NetworkComponentProps = RouteComponentProps<{
- detailName?: string;
- flowTarget?: string;
- search: string;
-}>;
-export const RedirectToNetworkPage = ({
- match: {
- params: { detailName, flowTarget },
- },
- location: { search },
-}: NetworkComponentProps) => (
-
-);
+export const getNetworkUrl = (search?: string) => `${appendSearch(search)}`;
-const baseNetworkUrl = `#/link-to/${SiemPageName.network}`;
-export const getNetworkUrl = (search?: string) => `${baseNetworkUrl}${appendSearch(search)}`;
export const getIPDetailsUrl = (
detailName: string,
- flowTarget?: FlowTarget | FlowTargetSourceDest
-) => `${baseNetworkUrl}/ip/${detailName}/${flowTarget || FlowTarget.source}`;
+ flowTarget?: FlowTarget | FlowTargetSourceDest,
+ search?: string
+) => `/ip/${detailName}/${flowTarget || FlowTarget.source}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx
index 2043b820e696..d0cf46bd9052 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx
@@ -4,17 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-import { RedirectWrapper } from './redirect_wrapper';
-import { SiemPageName } from '../../../app/types';
+import { APP_OVERVIEW_PATH } from '../../../../common/constants';
+import { appendSearch } from './helpers';
-export type OverviewComponentProps = RouteComponentProps<{
- search: string;
-}>;
-
-export const RedirectToOverviewPage = ({ location: { search } }: OverviewComponentProps) => (
-
-);
-
-export const getOverviewUrl = () => `#/link-to/${SiemPageName.overview}`;
+export const getAppOverviewUrl = (search?: string) => `${APP_OVERVIEW_PATH}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx
index 3562153bea64..75a2fa1efa41 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx
@@ -4,37 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-
-import { SiemPageName } from '../../../app/types';
-
+import { TimelineTypeLiteral } from '../../../../common/types/timeline';
import { appendSearch } from './helpers';
-import { RedirectWrapper } from './redirect_wrapper';
-import { TimelineTypeLiteral, TimelineType } from '../../../../common/types/timeline';
-
-export type TimelineComponentProps = RouteComponentProps<{
- tabName: TimelineTypeLiteral;
- search: string;
-}>;
-
-export const RedirectToTimelinesPage = ({
- match: {
- params: { tabName },
- },
- location: { search },
-}: TimelineComponentProps) => (
-
-);
-export const getTimelinesUrl = (search?: string) =>
- `#/link-to/${SiemPageName.timelines}${appendSearch(search)}`;
+export const getTimelinesUrl = (search?: string) => `${appendSearch(search)}`;
export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) =>
- `#/link-to/${SiemPageName.timelines}/${tabName}${appendSearch(search)}`;
+ `/${tabName}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_wrapper.tsx
deleted file mode 100644
index 39f24e8351f6..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_wrapper.tsx
+++ /dev/null
@@ -1,18 +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 React from 'react';
-import { Redirect } from 'react-router-dom';
-import { useScrollToTop } from '../scroll_to_top';
-
-export interface RedirectWrapperProps {
- to: string;
-}
-
-export const RedirectWrapper = ({ to }: RedirectWrapperProps) => {
- useScrollToTop();
- return ;
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
index 9eff86bffb36..b6817c4cab1f 100644
--- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
@@ -24,11 +24,20 @@ import {
ExternalLink,
} from '.';
+jest.mock('../link_to');
+
jest.mock('../../../overview/components/events_by_dataset');
jest.mock('../../lib/kibana', () => {
return {
useUiSetting$: jest.fn(),
+ useKibana: () => ({
+ services: {
+ application: {
+ navigateToApp: jest.fn(),
+ },
+ },
+ }),
};
});
@@ -41,17 +50,13 @@ describe('Custom Links', () => {
describe('HostDetailsLink', () => {
test('should render valid link to Host Details with hostName as the display text', () => {
const wrapper = mount( );
- expect(wrapper.find('EuiLink').prop('href')).toEqual(
- `#/link-to/hosts/${encodeURIComponent(hostName)}`
- );
+ expect(wrapper.find('EuiLink').prop('href')).toEqual(`/${encodeURIComponent(hostName)}`);
expect(wrapper.text()).toEqual(hostName);
});
test('should render valid link to Host Details with child text as the display text', () => {
const wrapper = mount({hostName} );
- expect(wrapper.find('EuiLink').prop('href')).toEqual(
- `#/link-to/hosts/${encodeURIComponent(hostName)}`
- );
+ expect(wrapper.find('EuiLink').prop('href')).toEqual(`/${encodeURIComponent(hostName)}`);
expect(wrapper.text()).toEqual(hostName);
});
});
@@ -60,7 +65,7 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with ipv4 as the display text', () => {
const wrapper = mount( );
expect(wrapper.find('EuiLink').prop('href')).toEqual(
- `#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
+ `/ip/${encodeURIComponent(ipv4)}/source`
);
expect(wrapper.text()).toEqual(ipv4);
});
@@ -68,7 +73,7 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with child text as the display text', () => {
const wrapper = mount({hostName} );
expect(wrapper.find('EuiLink').prop('href')).toEqual(
- `#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
+ `/ip/${encodeURIComponent(ipv4)}/source`
);
expect(wrapper.text()).toEqual(hostName);
});
@@ -76,7 +81,7 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with ipv6 as the display text', () => {
const wrapper = mount( );
expect(wrapper.find('EuiLink').prop('href')).toEqual(
- `#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}/source`
+ `/ip/${encodeURIComponent(ipv6Encoded)}/source`
);
expect(wrapper.text()).toEqual(ipv6);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
index 637f0d8d5305..3b92faff9151 100644
--- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
@@ -4,12 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiLink, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import React, { useMemo } from 'react';
+import {
+ EuiButton,
+ EuiButtonProps,
+ EuiLink,
+ EuiLinkProps,
+ EuiToolTip,
+ EuiFlexGroup,
+ EuiFlexItem,
+ PropsForAnchor,
+ PropsForButton,
+} from '@elastic/eui';
+import React, { useMemo, useCallback } from 'react';
import { isNil } from 'lodash/fp';
import styled from 'styled-components';
-import { IP_REPUTATION_LINKS_SETTING } from '../../../../common/constants';
+import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants';
import {
DefaultFieldRendererOverflow,
DEFAULT_MORE_MAX_HEIGHT,
@@ -20,27 +30,53 @@ import {
getHostDetailsUrl,
getIPDetailsUrl,
getCreateCaseUrl,
+ useFormatUrl,
} from '../link_to';
import { FlowTarget, FlowTargetSourceDest } from '../../../graphql/types';
-import { useUiSetting$ } from '../../lib/kibana';
+import { useUiSetting$, useKibana } from '../../lib/kibana';
import { isUrlInvalid } from '../../utils/validators';
import { ExternalLinkIcon } from '../external_link_icon';
-import { navTabs } from '../../../app/home/home_navigations';
-import { useGetUrlSearch } from '../navigation/use_get_url_search';
import * as i18n from './translations';
+import { SecurityPageName } from '../../../app/types';
export const DEFAULT_NUMBER_OF_LINK = 5;
+export const LinkButton: React.FC<
+ PropsForButton | PropsForAnchor
+> = ({ children, ...props }) => {children} ;
+
+export const LinkAnchor: React.FC = ({ children, ...props }) => (
+ {children}
+);
+
// Internal Links
const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: string }> = ({
children,
hostName,
-}) => (
-
- {children ? children : hostName}
-
-);
+}) => {
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.hosts);
+ const { navigateToApp } = useKibana().services.application;
+ const goToHostDetails = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getHostDetailsUrl(encodeURIComponent(hostName), search),
+ });
+ },
+ [hostName, navigateToApp, search]
+ );
+
+ return (
+
+ {children ? children : hostName}
+
+ );
+};
+export const HostDetailsLink = React.memo(HostDetailsLinkComponent);
const whitelistUrlSchemes = ['http://', 'https://'];
export const ExternalLink = React.memo<{
@@ -75,17 +111,32 @@ export const ExternalLink = React.memo<{
ExternalLink.displayName = 'ExternalLink';
-export const HostDetailsLink = React.memo(HostDetailsLinkComponent);
-
const IPDetailsLinkComponent: React.FC<{
children?: React.ReactNode;
ip: string;
flowTarget?: FlowTarget | FlowTargetSourceDest;
-}> = ({ children, ip, flowTarget = FlowTarget.source }) => (
-
- {children ? children : ip}
-
-);
+}> = ({ children, ip, flowTarget = FlowTarget.source }) => {
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.network);
+ const { navigateToApp } = useKibana().services.application;
+ const goToNetworkDetails = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.network}`, {
+ path: getIPDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search),
+ });
+ },
+ [flowTarget, ip, navigateToApp, search]
+ );
+
+ return (
+
+ {children ? children : ip}
+
+ );
+};
export const IPDetailsLink = React.memo(IPDetailsLinkComponent);
@@ -94,24 +145,49 @@ const CaseDetailsLinkComponent: React.FC<{
detailName: string;
title?: string;
}> = ({ children, detailName, title }) => {
- const search = useGetUrlSearch(navTabs.case);
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.case);
+ const { navigateToApp } = useKibana().services.application;
+ const goToCaseDetails = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCaseDetailsUrl({ id: detailName, search }),
+ });
+ },
+ [detailName, navigateToApp, search]
+ );
return (
-
{children ? children : detailName}
-
+
);
};
export const CaseDetailsLink = React.memo(CaseDetailsLinkComponent);
CaseDetailsLink.displayName = 'CaseDetailsLink';
export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ children }) => {
- const search = useGetUrlSearch(navTabs.case);
- return {children} ;
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.case);
+ const { navigateToApp } = useKibana().services.application;
+ const goToCreateCase = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCreateCaseUrl(search),
+ });
+ },
+ [navigateToApp, search]
+ );
+ return (
+
+ {children}
+
+ );
});
CreateCaseLink.displayName = 'CreateCaseLink';
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx
index 6ca723c50c68..0f3e0f9171e6 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx
@@ -11,7 +11,6 @@ import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
import { addEntitiesToKql } from './add_entities_to_kql';
import { replaceKQLParts } from './replace_kql_parts';
import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers';
-import { SiemPageName } from '../../../../app/types';
import { HostsTableType } from '../../../../hosts/store/model';
import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public';
@@ -42,7 +41,7 @@ export const MlHostConditionalContainer = React.memo(({
sort: false,
encode: false,
});
- return ;
+ return ;
}}
/>
(({
encode: false,
});
- return (
-
- );
+ return ;
} else if (multipleEntities(hostName)) {
const hosts: string[] = getMultipleEntities(hostName);
queryStringDecoded.query = addEntitiesToKql(
@@ -81,20 +78,14 @@ export const MlHostConditionalContainer = React.memo(({
encode: false,
});
- return (
-
- );
+ return ;
} else {
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
- return (
-
- );
+ return ;
}
}}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx
index 05049cd9b4ea..60242276dcad 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx
@@ -11,7 +11,6 @@ import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
import { addEntitiesToKql } from './add_entities_to_kql';
import { replaceKQLParts } from './replace_kql_parts';
import { emptyEntity, getMultipleEntities, multipleEntities } from './entity_helpers';
-import { SiemPageName } from '../../../../app/types';
import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public';
@@ -43,7 +42,7 @@ export const MlNetworkConditionalContainer = React.memo ;
+ return ;
}}
/>
;
+ return ;
} else if (multipleEntities(ip)) {
const ips: string[] = getMultipleEntities(ip);
queryStringDecoded.query = addEntitiesToKql(
@@ -80,13 +79,13 @@ export const MlNetworkConditionalContainer = React.memo ;
+ return ;
} else {
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
- return ;
+ return ;
}
}}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.test.ts
index 387b0f100188..4a25f82a94a6 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.test.ts
@@ -22,7 +22,7 @@ describe('create_explorer_link', () => {
new Date('3000').valueOf()
);
expect(entities).toEqual(
- "ml#/explorer?_g=(ml:(jobIds:!(job-1)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'1970-01-01T00:00:00.000Z',mode:absolute,to:'3000-01-01T00:00:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(),mlSelectLimit:(display:'10',val:10),mlShowCharts:!t)"
+ "#/explorer?_g=(ml:(jobIds:!(job-1)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'1970-01-01T00:00:00.000Z',mode:absolute,to:'3000-01-01T00:00:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(),mlSelectLimit:(display:'10',val:10),mlShowCharts:!t)"
);
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.ts b/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.tsx
similarity index 55%
rename from x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.ts
rename to x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.tsx
index 4e3efeb05e41..e00f53a08a91 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.ts
+++ b/x-pack/plugins/security_solution/public/common/components/ml/links/create_explorer_link.tsx
@@ -4,13 +4,42 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { EuiLink } from '@elastic/eui';
+import React from 'react';
import { Anomaly } from '../types';
+import { useKibana } from '../../../lib/kibana';
+
+interface ExplorerLinkProps {
+ score: Anomaly;
+ startDate: number;
+ endDate: number;
+ linkName: React.ReactNode;
+}
+
+export const ExplorerLink: React.FC = ({
+ score,
+ startDate,
+ endDate,
+ linkName,
+}) => {
+ const { getUrlForApp } = useKibana().services.application;
+ return (
+
+ {linkName}
+
+ );
+};
export const createExplorerLink = (score: Anomaly, startDate: number, endDate: number): string => {
const startDateIso = new Date(startDate).toISOString();
const endDateIso = new Date(endDate).toISOString();
- const JOB_PREFIX = `ml#/explorer?_g=(ml:(jobIds:!(${score.jobId}))`;
+ const JOB_PREFIX = `#/explorer?_g=(ml:(jobIds:!(${score.jobId}))`;
const REFRESH_INTERVAL = `,refreshInterval:(display:Off,pause:!f,value:0),time:(from:'${startDateIso}',mode:absolute,to:'${endDateIso}'))`;
const INTERVAL_SELECTION = `&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(),mlSelectLimit:(display:'10',val:10),mlShowCharts:!t)`;
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
index 1cf35aac1904..249cc1fe5f89 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
@@ -125,12 +125,77 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
-
- View in Machine Learning
-
+
,
"title":
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap
index 0a136eb31fd6..2e771f9f045b 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap
@@ -43,12 +43,77 @@ exports[`create_description_list renders correctly against snapshot 1`] = `
-
- View in Machine Learning
-
+
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx
index 0651bc587486..133f2407c45e 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/create_description_list.tsx
@@ -14,7 +14,7 @@ import { getScoreString } from './score_health';
import { PreferenceFormattedDate } from '../../formatted_date';
import { createInfluencers } from './../influencers/create_influencers';
import * as i18n from './translations';
-import { createExplorerLink } from '../links/create_explorer_link';
+import { ExplorerLink } from '../links/create_explorer_link';
const LargeScore = styled(EuiText)`
font-size: 45px;
@@ -51,9 +51,12 @@ export const createDescriptionList = (
{score.jobId}
-
- {i18n.VIEW_IN_MACHINE_LEARNING}
-
+
),
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.tsx
index 53da31af4ad6..fc89189bf4f4 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.tsx
@@ -7,7 +7,7 @@
/* eslint-disable react/display-name */
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Columns } from '../../paginated_table';
import { AnomaliesByHost, Anomaly, NarrowDateRange } from '../types';
import { getRowItemDraggable } from '../../tables/helpers';
@@ -18,7 +18,7 @@ import { HostDetailsLink } from '../../links';
import * as i18n from './translations';
import { getEntries } from '../get_entries';
import { DraggableScore } from '../score/draggable_score';
-import { createExplorerLink } from '../links/create_explorer_link';
+import { ExplorerLink } from '../links/create_explorer_link';
import { HostsType } from '../../../../hosts/store/model';
import { escapeDataProviderId } from '../../drag_and_drop/helpers';
import { FormattedRelativePreferenceDate } from '../../formatted_date';
@@ -55,12 +55,12 @@ export const getAnomaliesHostTableColumns = (
field: 'anomaly.jobId',
sortable: true,
render: (jobId, anomaliesByHost) => (
-
- {jobId}
-
+
),
},
{
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.tsx
index abd4eec8898e..ce4269afbe5b 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.tsx
@@ -7,7 +7,7 @@
/* eslint-disable react/display-name */
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Columns } from '../../paginated_table';
import { Anomaly, AnomaliesByNetwork } from '../types';
@@ -19,7 +19,7 @@ import { IPDetailsLink } from '../../links';
import * as i18n from './translations';
import { getEntries } from '../get_entries';
import { DraggableScore } from '../score/draggable_score';
-import { createExplorerLink } from '../links/create_explorer_link';
+import { ExplorerLink } from '../links/create_explorer_link';
import { FormattedRelativePreferenceDate } from '../../formatted_date';
import { NetworkType } from '../../../../network/store/model';
import { escapeDataProviderId } from '../../drag_and_drop/helpers';
@@ -54,12 +54,12 @@ export const getAnomaliesNetworkTableColumns = (
field: 'anomaly.jobId',
sortable: true,
render: (jobId, anomaliesByHost) => (
-
- {jobId}
-
+
),
},
{
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/__mocks__/use_get_url_search.ts b/x-pack/plugins/security_solution/public/common/components/navigation/__mocks__/use_get_url_search.ts
new file mode 100644
index 000000000000..d8a1db0aae70
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/__mocks__/use_get_url_search.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SearchNavTab } from '../types';
+
+export const useGetUrlSearch = (tab: SearchNavTab) => '';
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
index 8f1318aea376..2c30f9a2e4ac 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
@@ -38,28 +38,28 @@ const getMockObject = (
navTabs: {
hosts: {
disabled: false,
- href: '#/link-to/hosts',
+ href: '/app/security/hosts',
id: 'hosts',
name: 'Hosts',
urlKey: 'host',
},
network: {
disabled: false,
- href: '#/link-to/network',
+ href: '/app/security/network',
id: 'network',
name: 'Network',
urlKey: 'network',
},
overview: {
disabled: false,
- href: '#/link-to/overview',
+ href: '/app/security/overview',
id: 'overview',
name: 'Overview',
urlKey: 'overview',
},
timelines: {
disabled: false,
- href: '#/link-to/timelines',
+ href: '/app/security/timelines',
id: 'timelines',
name: 'Timelines',
urlKey: 'timeline',
@@ -99,6 +99,9 @@ const getMockObject = (
},
});
+const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) =>
+ `${appId}${options?.path ?? ''}`;
+
describe('Navigation Breadcrumbs', () => {
const hostName = 'siem-kibana';
@@ -108,15 +111,18 @@ describe('Navigation Breadcrumbs', () => {
describe('getBreadcrumbsForRoute', () => {
test('should return Host breadcrumbs when supplied host pathname', () => {
- const breadcrumbs = getBreadcrumbsForRoute(getMockObject('hosts', '/hosts', undefined));
+ const breadcrumbs = getBreadcrumbsForRoute(
+ getMockObject('hosts', '/', undefined),
+ getUrlForAppMock
+ );
expect(breadcrumbs).toEqual([
{
- href: '#/link-to/overview',
+ href: '/app/security/overview',
text: 'Security',
},
{
href:
- '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
text: 'Hosts',
},
{
@@ -127,13 +133,16 @@ describe('Navigation Breadcrumbs', () => {
});
test('should return Network breadcrumbs when supplied network pathname', () => {
- const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', undefined));
+ const breadcrumbs = getBreadcrumbsForRoute(
+ getMockObject('network', '/', undefined),
+ getUrlForAppMock
+ );
expect(breadcrumbs).toEqual([
- { text: 'Security', href: '#/link-to/overview' },
+ { text: 'Security', href: '/app/security/overview' },
{
text: 'Network',
href:
- '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: 'Flows',
@@ -144,61 +153,75 @@ describe('Navigation Breadcrumbs', () => {
test('should return Timelines breadcrumbs when supplied timelines pathname', () => {
const breadcrumbs = getBreadcrumbsForRoute(
- getMockObject('timelines', '/timelines', undefined)
+ getMockObject('timelines', '/', undefined),
+ getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: '#/link-to/overview' },
- { text: 'Timelines', href: '#/link-to/timelines' },
+ { text: 'Security', href: '/app/security/overview' },
+ {
+ text: 'Timelines',
+ href:
+ 'securitySolution:timelines?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ },
]);
});
test('should return Host Details breadcrumbs when supplied a pathname with hostName', () => {
- const breadcrumbs = getBreadcrumbsForRoute(getMockObject('hosts', '/hosts', hostName));
+ const breadcrumbs = getBreadcrumbsForRoute(
+ getMockObject('hosts', '/', hostName),
+ getUrlForAppMock
+ );
expect(breadcrumbs).toEqual([
- { text: 'Security', href: '#/link-to/overview' },
+ { text: 'Security', href: '/app/security/overview' },
{
text: 'Hosts',
href:
- '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: 'siem-kibana',
href:
- '#/link-to/hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{ text: 'Authentications', href: '' },
]);
});
test('should return IP Details breadcrumbs when supplied pathname with ipv4', () => {
- const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', ipv4));
+ const breadcrumbs = getBreadcrumbsForRoute(
+ getMockObject('network', '/', ipv4),
+ getUrlForAppMock
+ );
expect(breadcrumbs).toEqual([
- { text: 'Security', href: '#/link-to/overview' },
+ { text: 'Security', href: '/app/security/overview' },
{
text: 'Network',
href:
- '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: ipv4,
- href: `#/link-to/network/ip/${ipv4}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
+ href: `securitySolution:network/ip/${ipv4}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
},
{ text: 'Flows', href: '' },
]);
});
test('should return IP Details breadcrumbs when supplied pathname with ipv6', () => {
- const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', ipv6Encoded));
+ const breadcrumbs = getBreadcrumbsForRoute(
+ getMockObject('network', '/', ipv6Encoded),
+ getUrlForAppMock
+ );
expect(breadcrumbs).toEqual([
- { text: 'Security', href: '#/link-to/overview' },
+ { text: 'Security', href: '/app/security/overview' },
{
text: 'Network',
href:
- '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: ipv6,
- href: `#/link-to/network/ip/${ipv6Encoded}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
+ href: `securitySolution:network/ip/${ipv6Encoded}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
},
{ text: 'Flows', href: '' },
]);
@@ -207,18 +230,18 @@ describe('Navigation Breadcrumbs', () => {
describe('setBreadcrumbs()', () => {
test('should call chrome breadcrumb service with correct breadcrumbs', () => {
- setBreadcrumbs(getMockObject('hosts', '/hosts', hostName), chromeMock);
+ setBreadcrumbs(getMockObject('hosts', '/', hostName), chromeMock, getUrlForAppMock);
expect(setBreadcrumbsMock).toBeCalledWith([
- { text: 'Security', href: '#/link-to/overview' },
+ { text: 'Security', href: '/app/security/overview' },
{
text: 'Hosts',
href:
- '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: 'siem-kibana',
href:
- '#/link-to/hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
+ 'securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{ text: 'Authentications', href: '' },
]);
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
index a202407b1270..5c1c68b80272 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
@@ -15,24 +15,25 @@ import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/p
import { getBreadcrumbs as getCaseDetailsBreadcrumbs } from '../../../../cases/pages/utils';
import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../alerts/pages/detection_engine/rules/utils';
import { getBreadcrumbs as getTimelinesBreadcrumbs } from '../../../../timelines/pages';
-import { SiemPageName } from '../../../../app/types';
+import { SecurityPageName } from '../../../../app/types';
import {
RouteSpyState,
HostRouteSpyState,
NetworkRouteSpyState,
TimelineRouteSpyState,
} from '../../../utils/route/types';
-import { getOverviewUrl } from '../../link_to';
+import { getAppOverviewUrl } from '../../link_to';
import { TabNavigationProps } from '../tab_navigation/types';
import { getSearch } from '../helpers';
-import { SearchNavTab } from '../types';
+import { GetUrlForApp, SearchNavTab } from '../types';
export const setBreadcrumbs = (
spyState: RouteSpyState & TabNavigationProps,
- chrome: StartServices['chrome']
+ chrome: StartServices['chrome'],
+ getUrlForApp: GetUrlForApp
) => {
- const breadcrumbs = getBreadcrumbsForRoute(spyState);
+ const breadcrumbs = getBreadcrumbsForRoute(spyState, getUrlForApp);
if (breadcrumbs) {
chrome.setBreadcrumbs(breadcrumbs);
}
@@ -41,27 +42,28 @@ export const setBreadcrumbs = (
export const siemRootBreadcrumb: ChromeBreadcrumb[] = [
{
text: APP_NAME,
- href: getOverviewUrl(),
+ href: getAppOverviewUrl(),
},
];
const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpyState =>
- spyState != null && spyState.pageName === SiemPageName.network;
+ spyState != null && spyState.pageName === SecurityPageName.network;
const isHostsRoutes = (spyState: RouteSpyState): spyState is HostRouteSpyState =>
- spyState != null && spyState.pageName === SiemPageName.hosts;
+ spyState != null && spyState.pageName === SecurityPageName.hosts;
const isTimelinesRoutes = (spyState: RouteSpyState): spyState is TimelineRouteSpyState =>
- spyState != null && spyState.pageName === SiemPageName.timelines;
+ spyState != null && spyState.pageName === SecurityPageName.timelines;
const isCaseRoutes = (spyState: RouteSpyState): spyState is RouteSpyState =>
- spyState != null && spyState.pageName === SiemPageName.case;
+ spyState != null && spyState.pageName === SecurityPageName.case;
-const isDetectionsRoutes = (spyState: RouteSpyState) =>
- spyState != null && spyState.pageName === SiemPageName.detections;
+const isAlertsRoutes = (spyState: RouteSpyState) =>
+ spyState != null && spyState.pageName === SecurityPageName.alerts;
export const getBreadcrumbsForRoute = (
- object: RouteSpyState & TabNavigationProps
+ object: RouteSpyState & TabNavigationProps,
+ getUrlForApp: GetUrlForApp
): ChromeBreadcrumb[] | null => {
const spyState: RouteSpyState = omit('navTabs', object);
if (isHostsRoutes(spyState) && object.navTabs) {
@@ -77,7 +79,8 @@ export const getBreadcrumbsForRoute = (
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
- )
+ ),
+ getUrlForApp
),
];
}
@@ -94,12 +97,13 @@ export const getBreadcrumbsForRoute = (
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
- )
+ ),
+ getUrlForApp
),
];
}
- if (isDetectionsRoutes(spyState) && object.navTabs) {
- const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false };
+ if (isAlertsRoutes(spyState) && object.navTabs) {
+ const tempNav: SearchNavTab = { urlKey: 'alerts', isDetailPage: false };
let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
if (spyState.tabName != null) {
urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
@@ -112,7 +116,8 @@ export const getBreadcrumbsForRoute = (
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
- )
+ ),
+ getUrlForApp
),
];
}
@@ -130,7 +135,8 @@ export const getBreadcrumbsForRoute = (
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
- )
+ ),
+ getUrlForApp
),
];
}
@@ -148,7 +154,8 @@ export const getBreadcrumbsForRoute = (
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
- )
+ ),
+ getUrlForApp
),
];
}
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
index fd96885e5bc1..69e6ef8688c4 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
@@ -15,14 +15,36 @@ import { HostsTableType } from '../../../hosts/store/model';
import { RouteSpyState } from '../../utils/route/types';
import { SiemNavigationProps, SiemNavigationComponentProps } from './types';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
jest.mock('./breadcrumbs', () => ({
setBreadcrumbs: jest.fn(),
}));
+const mockGetUrlForApp = jest.fn();
+jest.mock('../../lib/kibana', () => {
+ return {
+ useKibana: () => ({
+ services: {
+ chrome: undefined,
+ application: {
+ navigateToApp: jest.fn(),
+ getUrlForApp: mockGetUrlForApp,
+ },
+ },
+ }),
+ };
+});
+jest.mock('../link_to');
describe('SIEM Navigation', () => {
const mockProps: SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState = {
pageName: 'hosts',
- pathName: '/hosts',
+ pathName: '/',
detailName: undefined,
search: '',
tabName: HostsTableType.authentications,
@@ -65,63 +87,64 @@ describe('SIEM Navigation', () => {
{
detailName: undefined,
navTabs: {
+ alerts: {
+ disabled: false,
+ href: '/app/security/alerts',
+ id: 'alerts',
+ name: 'Alerts',
+ urlKey: 'alerts',
+ },
case: {
disabled: false,
- href: '#/link-to/case',
+ href: '/app/security/cases',
id: 'case',
name: 'Cases',
urlKey: 'case',
},
management: {
disabled: false,
- href: '#/management',
+ href: '/app/security/management',
id: 'management',
name: 'Management',
urlKey: 'management',
},
- detections: {
- disabled: false,
- href: '#/link-to/detections',
- id: 'detections',
- name: 'Detections',
- urlKey: 'detections',
- },
hosts: {
disabled: false,
- href: '#/link-to/hosts',
+ href: '/app/security/hosts',
id: 'hosts',
name: 'Hosts',
urlKey: 'host',
},
network: {
disabled: false,
- href: '#/link-to/network',
+ href: '/app/security/network',
id: 'network',
name: 'Network',
urlKey: 'network',
},
overview: {
disabled: false,
- href: '#/link-to/overview',
+ href: '/app/security/overview',
id: 'overview',
name: 'Overview',
urlKey: 'overview',
},
timelines: {
disabled: false,
- href: '#/link-to/timelines',
+ href: '/app/security/timelines',
id: 'timelines',
name: 'Timelines',
urlKey: 'timeline',
},
},
pageName: 'hosts',
- pathName: '/hosts',
+ pathName: '/',
search: '',
state: undefined,
tabName: 'authentications',
query: { query: '', language: 'kuery' },
filters: [],
+ flowTarget: undefined,
savedQuery: undefined,
timeline: {
id: '',
@@ -150,79 +173,82 @@ describe('SIEM Navigation', () => {
},
},
},
- undefined
+ undefined,
+ mockGetUrlForApp
);
});
test('it calls setBreadcrumbs with correct path on update', () => {
wrapper.setProps({
pageName: 'network',
- pathName: '/network',
+ pathName: '/',
tabName: undefined,
});
wrapper.update();
expect(setBreadcrumbs).toHaveBeenNthCalledWith(
- 1,
+ 2,
{
detailName: undefined,
filters: [],
+ flowTarget: undefined,
navTabs: {
+ alerts: {
+ disabled: false,
+ href: '/app/security/alerts',
+ id: 'alerts',
+ name: 'Alerts',
+ urlKey: 'alerts',
+ },
case: {
disabled: false,
- href: '#/link-to/case',
+ href: '/app/security/cases',
id: 'case',
name: 'Cases',
urlKey: 'case',
},
- detections: {
- disabled: false,
- href: '#/link-to/detections',
- id: 'detections',
- name: 'Detections',
- urlKey: 'detections',
- },
+
hosts: {
disabled: false,
- href: '#/link-to/hosts',
+ href: '/app/security/hosts',
id: 'hosts',
name: 'Hosts',
urlKey: 'host',
},
management: {
disabled: false,
- href: '#/management',
+ href: '/app/security/management',
id: 'management',
name: 'Management',
urlKey: 'management',
},
network: {
disabled: false,
- href: '#/link-to/network',
+ href: '/app/security/network',
id: 'network',
name: 'Network',
urlKey: 'network',
},
overview: {
disabled: false,
- href: '#/link-to/overview',
+ href: '/app/security/overview',
id: 'overview',
name: 'Overview',
urlKey: 'overview',
},
timelines: {
disabled: false,
- href: '#/link-to/timelines',
+ href: '/app/security/timelines',
id: 'timelines',
name: 'Timelines',
urlKey: 'timeline',
},
},
- pageName: 'hosts',
- pathName: '/hosts',
+ pageName: 'network',
+ pathName: '/',
query: { language: 'kuery', query: '' },
savedQuery: undefined,
search: '',
state: undefined,
- tabName: 'authentications',
+ tabName: undefined,
timeline: { id: '', isOpen: false },
timerange: {
global: {
@@ -247,7 +273,8 @@ describe('SIEM Navigation', () => {
},
},
},
- undefined
+ undefined,
+ mockGetUrlForApp
);
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
index 0cbff9e70eff..5ee35e7da0f3 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
@@ -31,10 +31,13 @@ export const SiemNavigationComponent: React.FC<
flowTarget,
state,
}) => {
- const { chrome } = useKibana().services;
+ const {
+ chrome,
+ application: { getUrlForApp },
+ } = useKibana().services;
useEffect(() => {
- if (pathName) {
+ if (pathName || pageName) {
setBreadcrumbs(
{
query: urlState.query,
@@ -51,11 +54,12 @@ export const SiemNavigationComponent: React.FC<
timeline: urlState.timeline,
state,
},
- chrome
+ chrome,
+ getUrlForApp
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [chrome, pathName, search, navTabs, urlState, state]);
+ }, [chrome, pageName, pathName, search, navTabs, urlState, state]);
return (
({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ push: jest.fn(),
+ }),
+}));
+
describe('Tab Navigation', () => {
- const pageName = SiemPageName.hosts;
+ const pageName = SecurityPageName.hosts;
const hostName = 'siem-window';
const tabName = HostsTableType.authentications;
const pathName = `/${pageName}/${hostName}/${tabName}`;
@@ -76,13 +86,6 @@ describe('Tab Navigation', () => {
wrapper.update();
expect(networkTab().prop('isSelected')).toBeTruthy();
});
- test('it carries the url state in the link', () => {
- const wrapper = mount( );
- const firstTab = wrapper.find('EuiTab[data-test-subj="navigation-network"]');
- expect(firstTab.props().href).toBe(
- "#/link-to/network?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))"
- );
- });
});
describe('Table Navigation', () => {
@@ -137,8 +140,8 @@ describe('Tab Navigation', () => {
wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first();
expect(tableNavigationTab().prop('isSelected')).toBeFalsy();
wrapper.setProps({
- pageName: SiemPageName.hosts,
- pathName: `/${SiemPageName.hosts}`,
+ pageName: SecurityPageName.hosts,
+ pathName: `/${SecurityPageName.hosts}`,
tabName: HostsTableType.events,
});
wrapper.update();
@@ -149,9 +152,7 @@ describe('Tab Navigation', () => {
const firstTab = wrapper.find(
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
);
- expect(firstTab.props().href).toBe(
- `#/${pageName}/${hostName}/${HostsTableType.authentications}?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`
- );
+ expect(firstTab.props().href).toBe('/siem-window/authentications');
});
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
index 15dff1ca82d8..217ad0e58570 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
@@ -6,30 +6,54 @@
import { EuiTab, EuiTabs } from '@elastic/eui';
import { getOr } from 'lodash/fp';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+import { APP_ID } from '../../../../../common/constants';
import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry';
import { getSearch } from '../helpers';
import { TabNavigationProps, TabNavigationItemProps } from './types';
+import { useKibana } from '../../../lib/kibana';
+import { SecurityPageName } from '../../../../app/types';
+import { useFormatUrl } from '../../link_to';
const TabNavigationItemComponent = ({
+ disabled,
href,
hrefWithSearch,
id,
- disabled,
name,
isSelected,
+ pageId,
+ urlSearch,
}: TabNavigationItemProps) => {
- const handleClick = useCallback(() => {
- track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
- }, [id]);
-
+ const history = useHistory();
+ const { navigateToApp, getUrlForApp } = useKibana().services.application;
+ const { formatUrl } = useFormatUrl(((pageId ?? id) as unknown) as SecurityPageName);
+ const handleClick = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ if (id in SecurityPageName && pageId == null) {
+ navigateToApp(`${APP_ID}:${id}`, { path: urlSearch });
+ } else {
+ history.push(hrefWithSearch);
+ }
+ track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
+ },
+ [history, hrefWithSearch, id, navigateToApp, pageId, urlSearch]
+ );
+ const appHref =
+ pageId != null
+ ? formatUrl(href)
+ : getUrlForApp(`${APP_ID}:${id}`, {
+ path: urlSearch,
+ });
return (
{name}
@@ -47,7 +71,11 @@ export const TabNavigationComponent = (props: TabNavigationProps) => {
getOr(
'',
'id',
- Object.values(navTabs).find((item) => tabName === item.id || pageName === item.id)
+ Object.values(navTabs).find(
+ (item) =>
+ (tabName === item.id && item.pageId != null) ||
+ (pageName === item.id && item.pageId == null)
+ )
),
[pageName, tabName, navTabs]
);
@@ -67,6 +95,7 @@ export const TabNavigationComponent = (props: TabNavigationProps) => {
Object.values(navTabs).map((tab) => {
const isSelected = selectedTabId === tab.id;
const { query, filters, savedQuery, timerange, timeline } = props;
+ const search = getSearch(tab, { query, filters, savedQuery, timerange, timeline });
const hrefWithSearch =
tab.href + getSearch(tab, { query, filters, savedQuery, timerange, timeline });
@@ -75,10 +104,12 @@ export const TabNavigationComponent = (props: TabNavigationProps) => {
key={`navigation-${tab.id}`}
id={tab.id}
href={tab.href}
+ hrefWithSearch={hrefWithSearch}
name={tab.name}
disabled={tab.disabled}
- hrefWithSearch={hrefWithSearch}
+ pageId={tab.pageId}
isSelected={isSelected}
+ urlSearch={search}
/>
);
}),
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts
index a283691cfe0d..7f72e37c74d5 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts
@@ -30,4 +30,6 @@ export interface TabNavigationItemProps {
disabled: boolean;
name: string;
isSelected: boolean;
+ urlSearch: string;
+ pageId?: string;
}
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
index f0256813c29e..80302be18355 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
@@ -9,6 +9,7 @@ import { HostsTableType } from '../../../hosts/store/model';
import { UrlInputsModel } from '../../store/inputs/model';
import { TimelineUrl } from '../../../timelines/store/timeline/model';
import { CONSTANTS, UrlStateType } from '../url_state/constants';
+import { SecurityPageName } from '../../../app/types';
export interface SiemNavigationProps {
display?: 'default' | 'condensed';
@@ -37,4 +38,21 @@ export interface NavTab {
disabled: boolean;
urlKey: UrlStateType;
isDetailPage?: boolean;
+ pageId?: SecurityPageName;
}
+
+export type SiemNavTabKey =
+ | SecurityPageName.overview
+ | SecurityPageName.hosts
+ | SecurityPageName.network
+ | SecurityPageName.alerts
+ | SecurityPageName.timelines
+ | SecurityPageName.case
+ | SecurityPageName.management;
+
+export type SiemNavTab = Record;
+
+export type GetUrlForApp = (
+ appId: string,
+ options?: { path?: string; absolute?: boolean }
+) => string;
diff --git a/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx b/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx
index 8061a8f9799e..d626433de1b6 100644
--- a/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx
@@ -8,17 +8,21 @@ import { EuiLink, EuiText } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';
+import { useBasePath } from '../../../lib/kibana';
-export const NoNews = React.memo(() => (
- <>
-
- {i18n.NO_NEWS_MESSAGE}{' '}
-
- {i18n.ADVANCED_SETTINGS_LINK_TITLE}
-
- {'.'}
-
- >
-));
+export const NoNews = React.memo(() => {
+ const basePath = useBasePath();
+ return (
+ <>
+
+ {i18n.NO_NEWS_MESSAGE}{' '}
+
+ {i18n.ADVANCED_SETTINGS_LINK_TITLE}
+
+ {'.'}
+
+ >
+ );
+});
NoNews.displayName = 'NoNews';
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
index 1c24c325dc9e..d545de9e7a6b 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
@@ -26,6 +26,14 @@ import {
timelineDefaults,
} from '../../../timelines/components/manage_timeline';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
+jest.mock('../link_to');
jest.mock('../../lib/kibana');
jest.mock('../../../timelines/store/timeline/actions');
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
index 5e1476f62216..62960d4fae71 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
@@ -13,7 +13,15 @@ import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { allEvents, defaultOptions } from './helpers';
import { TopN } from './top_n';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+}));
+
jest.mock('../../lib/kibana');
+jest.mock('../link_to');
jest.mock('uuid', () => {
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
index 1faff2594ce8..71faec88e85a 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
@@ -8,7 +8,7 @@ export enum CONSTANTS {
appQuery = 'query',
caseDetails = 'case.details',
casePage = 'case.page',
- detectionsPage = 'detections.page',
+ alertsPage = 'alerts.page',
filters = 'filters',
hostsDetails = 'hosts.details',
hostsPage = 'hosts.page',
@@ -25,7 +25,7 @@ export enum CONSTANTS {
export type UrlStateType =
| 'case'
- | 'detections'
+ | 'alerts'
| 'host'
| 'network'
| 'overview'
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
index 8f13e4dd0cdc..c270a99d3c51 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
@@ -12,7 +12,7 @@ import * as H from 'history';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
import { url } from '../../../../../../../src/plugins/kibana_utils/public';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
import { inputsSelectors, State } from '../../store';
import { UrlInputsModel } from '../../store/inputs/model';
import { TimelineUrl } from '../../../timelines/store/timeline/model';
@@ -84,17 +84,17 @@ export const replaceQueryStringInLocation = (
};
export const getUrlType = (pageName: string): UrlStateType => {
- if (pageName === SiemPageName.overview) {
+ if (pageName === SecurityPageName.overview) {
return 'overview';
- } else if (pageName === SiemPageName.hosts) {
+ } else if (pageName === SecurityPageName.hosts) {
return 'host';
- } else if (pageName === SiemPageName.network) {
+ } else if (pageName === SecurityPageName.network) {
return 'network';
- } else if (pageName === SiemPageName.detections) {
- return 'detections';
- } else if (pageName === SiemPageName.timelines) {
+ } else if (pageName === SecurityPageName.alerts) {
+ return 'alerts';
+ } else if (pageName === SecurityPageName.timelines) {
return 'timeline';
- } else if (pageName === SiemPageName.case) {
+ } else if (pageName === SecurityPageName.case) {
return 'case';
}
return 'overview';
@@ -114,19 +114,20 @@ export const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
const getGlobalSavedQuerySelector = inputsSelectors.globalSavedQuerySelector();
- const getTimelines = timelineSelectors.getTimelines();
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
const mapStateToProps = (state: State) => {
const inputState = getInputsSelector(state);
const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global;
const { linkTo: timelineLinkTo, timerange: timelineTimerange } = inputState.timeline;
- const timeline = Object.entries(getTimelines(state)).reduce(
- (obj, [timelineId, timelineObj]) => ({
- id: timelineObj.savedObjectId != null ? timelineObj.savedObjectId : '',
- isOpen: timelineObj.show,
- }),
- { id: '', isOpen: false }
- );
+ const flyoutTimeline = getTimeline(state, 'timeline-1');
+ const timeline =
+ flyoutTimeline != null
+ ? {
+ id: flyoutTimeline.savedObjectId != null ? flyoutTimeline.savedObjectId : '',
+ isOpen: flyoutTimeline.show,
+ }
+ : { id: '', isOpen: false };
let searchAttr: {
[CONSTANTS.appQuery]?: Query;
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
index 138ab08e894a..20374affbdf8 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
@@ -8,7 +8,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { HookWrapper } from '../../mock';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
import { RouteSpyState } from '../../utils/route/types';
import { CONSTANTS } from './constants';
import {
@@ -26,7 +26,7 @@ import { wait } from '../../lib/helpers';
let mockProps: UrlStateContainerPropTypes;
const mockRouteSpy: RouteSpyState = {
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
tabName: undefined,
search: '',
@@ -186,14 +186,14 @@ describe('UrlStateContainer', () => {
page: CONSTANTS.hostsPage,
examplePath: '/hosts',
namespaceLower: 'hosts',
- pageName: SiemPageName.hosts,
+ pageName: SecurityPageName.hosts,
detailName: undefined,
}).relativeTimeSearch.undefinedQuery,
});
wrapper.update();
await wait();
- if (CONSTANTS.detectionsPage === page) {
+ if (CONSTANTS.alertsPage === page) {
expect(mockSetRelativeRangeDatePicker.mock.calls[3][0]).toEqual({
from: 11223344556677,
fromStr: 'now-1d/d',
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
index 1650e3bea202..f7502661da30 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
@@ -8,7 +8,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { HookWrapper } from '../../mock/hook_wrapper';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
import { CONSTANTS } from './constants';
import { getFilterQuery, getMockPropsObj, mockHistory, testCases } from './test_dependencies';
@@ -42,7 +42,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
page: CONSTANTS.networkPage,
examplePath: '/network',
namespaceLower: 'network',
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
}).noSearch.definedQuery;
const wrapper = mount(
@@ -93,7 +93,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
page: CONSTANTS.networkPage,
examplePath: '/network',
namespaceLower: 'network',
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
}).noSearch.undefinedQuery;
const wrapper = mount(
@@ -124,7 +124,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
page: CONSTANTS.networkPage,
examplePath: '/network',
namespaceLower: 'network',
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
}).noSearch.undefinedQuery;
@@ -187,14 +187,14 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
page: CONSTANTS.hostsPage,
examplePath: '/hosts',
namespaceLower: 'hosts',
- pageName: SiemPageName.hosts,
+ pageName: SecurityPageName.hosts,
detailName: undefined,
}).noSearch.undefinedQuery;
const updatedProps = getMockPropsObj({
page: CONSTANTS.networkPage,
examplePath: '/network',
namespaceLower: 'network',
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
}).noSearch.definedQuery;
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
index 0c69e24fb747..dec1672b076e 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
@@ -7,7 +7,7 @@
import { ActionCreator } from 'typescript-fsa';
import { DispatchUpdateTimeline } from '../../../timelines/components/open_timeline/types';
import { navTabs } from '../../../app/home/home_navigations';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
import { inputsActions } from '../../store/actions';
import { CONSTANTS } from './constants';
@@ -71,7 +71,7 @@ export const mockHistory = {
};
export const defaultProps: UrlStateContainerPropTypes = {
- pageName: SiemPageName.network,
+ pageName: SecurityPageName.network,
detailName: undefined,
tabName: HostsTableType.authentications,
search: '',
@@ -292,7 +292,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Network',
/* pathName */ '/network',
/* type */ networkModel.NetworkType.page,
- /* pageName */ SiemPageName.network,
+ /* pageName */ SecurityPageName.network,
/* detailName */ undefined,
],
[
@@ -301,7 +301,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Hosts',
/* pathName */ '/hosts',
/* type */ hostsModel.HostsType.page,
- /* pageName */ SiemPageName.hosts,
+ /* pageName */ SecurityPageName.hosts,
/* detailName */ undefined,
],
[
@@ -310,7 +310,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Hosts',
/* pathName */ '/hosts/siem-es',
/* type */ hostsModel.HostsType.details,
- /* pageName */ SiemPageName.hosts,
+ /* pageName */ SecurityPageName.hosts,
/* detailName */ 'host-test',
],
[
@@ -319,7 +319,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Network',
/* pathName */ '/network/ip/100.90.80',
/* type */ networkModel.NetworkType.details,
- /* pageName */ SiemPageName.network,
+ /* pageName */ SecurityPageName.network,
/* detailName */ '100.90.80',
],
[
@@ -328,7 +328,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Overview',
/* pathName */ '/overview',
/* type */ null,
- /* pageName */ SiemPageName.overview,
+ /* pageName */ SecurityPageName.overview,
/* detailName */ undefined,
],
[
@@ -337,7 +337,7 @@ export const testCases: Array<[
/* namespaceUpper */ 'Timeline',
/* pathName */ '/timeline',
/* type */ null,
- /* pageName */ SiemPageName.timelines,
+ /* pageName */ SecurityPageName.timelines,
/* detailName */ undefined,
],
];
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
index 8881a82e5cd1..8ca43cb576d3 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
@@ -32,7 +32,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
];
export const URL_STATE_KEYS: Record = {
- detections: [
+ alerts: [
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
@@ -80,7 +80,7 @@ export const URL_STATE_KEYS: Record = {
export type LocationTypes =
| CONSTANTS.caseDetails
| CONSTANTS.casePage
- | CONSTANTS.detectionsPage
+ | CONSTANTS.alertsPage
| CONSTANTS.hostsDetails
| CONSTANTS.hostsPage
| CONSTANTS.networkDetails
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx
index ef60967e70ac..221df436402d 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx
@@ -27,7 +27,7 @@ import {
ALL_URL_STATE_KEYS,
UrlStateToRedux,
} from './types';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
function usePrevious(value: PreviousLocationUrlState) {
const ref = useRef(value);
@@ -203,7 +203,7 @@ export const useUrlStateHooks = ({
}
});
} else if (pathName !== prevProps.pathName) {
- handleInitialize(type, pageName === SiemPageName.detections);
+ handleInitialize(type, pageName === SecurityPageName.alerts);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isInitializing, history, pathName, pageName, prevProps, urlState]);
diff --git a/x-pack/plugins/security_solution/public/common/lib/compose/kibana_compose.tsx b/x-pack/plugins/security_solution/public/common/lib/compose/kibana_compose.tsx
index f7c7c6531848..47834f148c91 100644
--- a/x-pack/plugins/security_solution/public/common/lib/compose/kibana_compose.tsx
+++ b/x-pack/plugins/security_solution/public/common/lib/compose/kibana_compose.tsx
@@ -14,7 +14,7 @@ import introspectionQueryResultData from '../../../graphql/introspection.json';
import { AppFrontendLibs } from '../lib';
import { getLinks } from './helpers';
-export function compose(core: CoreStart): AppFrontendLibs {
+export function composeLibs(core: CoreStart): AppFrontendLibs {
const cache = new InMemoryCache({
dataIdFromObject: () => null,
fragmentMatcher: new IntrospectionFragmentMatcher({
diff --git a/x-pack/plugins/security_solution/public/common/utils/route/index.test.tsx b/x-pack/plugins/security_solution/public/common/utils/route/index.test.tsx
index 95e40b0f6630..7246259f5afa 100644
--- a/x-pack/plugins/security_solution/public/common/utils/route/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/utils/route/index.test.tsx
@@ -71,13 +71,13 @@ describe('Spy Routes', () => {
path: pathname,
url: pathname,
params: {
- pageName: undefined,
detailName: '',
tabName: HostsTableType.hosts,
search: '',
flowTarget: undefined,
},
}}
+ pageName={undefined}
/>
);
@@ -103,13 +103,13 @@ describe('Spy Routes', () => {
path: pathname,
url: pathname,
params: {
- pageName: 'hosts',
detailName: undefined,
tabName: HostsTableType.hosts,
search: '?IdoNotWantToSeeYou="true"',
flowTarget: undefined,
},
}}
+ pageName="hosts"
/>
);
@@ -124,9 +124,9 @@ describe('Spy Routes', () => {
expect(dispatchMock.mock.calls[1]).toEqual([
{
route: {
+ pageName: 'hosts',
detailName: undefined,
history: mockHistory,
- pageName: 'hosts',
pathName: pathname,
tabName: HostsTableType.hosts,
},
@@ -154,13 +154,13 @@ describe('Spy Routes', () => {
path: pathname,
url: pathname,
params: {
- pageName: 'hosts',
detailName: undefined,
tabName: HostsTableType.hosts,
search: '?IdoNotWantToSeeYou="true"',
flowTarget: undefined,
},
}}
+ pageName="hosts"
/>
);
diff --git a/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx b/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx
index 072bbcf31c90..589436b945a6 100644
--- a/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx
+++ b/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx
@@ -12,13 +12,16 @@ import deepEqual from 'fast-deep-equal';
import { SpyRouteProps } from './types';
import { useRouteSpy } from './use_route_spy';
-export const SpyRouteComponent = memo(
+export const SpyRouteComponent = memo<
+ SpyRouteProps & { location: H.Location; pageName: string | undefined }
+>(
({
location: { pathname, search },
history,
match: {
- params: { pageName, detailName, tabName, flowTarget },
+ params: { detailName, tabName, flowTarget },
},
+ pageName,
state,
}) => {
const [isInitializing, setIsInitializing] = useState(true);
diff --git a/x-pack/plugins/security_solution/public/common/utils/route/types.ts b/x-pack/plugins/security_solution/public/common/utils/route/types.ts
index 912da545a66a..8656f20c9295 100644
--- a/x-pack/plugins/security_solution/public/common/utils/route/types.ts
+++ b/x-pack/plugins/security_solution/public/common/utils/route/types.ts
@@ -60,7 +60,6 @@ export interface ManageRoutesSpyProps {
}
export type SpyRouteProps = RouteComponentProps<{
- pageName: string | undefined;
detailName: string | undefined;
tabName: HostsTableType | undefined;
search: string;
diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx
index 37c6a6583f0f..fde3f6f8b222 100644
--- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx
@@ -4,21 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useLocation } from 'react-router-dom';
-
import { useState, useEffect } from 'react';
-import { SiemPageName } from '../../../app/types';
+import { useRouteSpy } from '../route/use_route_spy';
-const hideTimelineForRoutes = [`/${SiemPageName.case}/configure`];
+const hideTimelineForRoutes = [`/cases/configure`];
export const useShowTimeline = () => {
- const currentLocation = useLocation();
+ const [{ pageName, pathName }] = useRouteSpy();
+
const [showTimeline, setShowTimeline] = useState(
- !hideTimelineForRoutes.includes(currentLocation.pathname)
+ !hideTimelineForRoutes.includes(window.location.pathname)
);
useEffect(() => {
- if (hideTimelineForRoutes.includes(currentLocation.pathname)) {
+ if (
+ hideTimelineForRoutes.filter((route) => window.location.pathname.includes(route)).length > 0
+ ) {
if (showTimeline) {
setShowTimeline(false);
}
@@ -26,7 +27,7 @@ export const useShowTimeline = () => {
setShowTimeline(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentLocation.pathname]);
+ }, [pageName, pathName]);
return [showTimeline];
};
diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/index.ts b/x-pack/plugins/security_solution/public/endpoint_alerts/index.ts
index e7e45b95ceb9..a755b53728e1 100644
--- a/x-pack/plugins/security_solution/public/endpoint_alerts/index.ts
+++ b/x-pack/plugins/security_solution/public/endpoint_alerts/index.ts
@@ -6,7 +6,7 @@
import { Reducer } from 'redux';
import { SecuritySubPluginWithStore } from '../app/types';
-import { endpointAlertsRoutes } from './routes';
+import { EndpointAlertsRoutes } from './routes';
import { alertListReducer } from './store/reducer';
import { AlertListState } from '../../common/endpoint_alerts/types';
import { alertMiddlewareFactory } from './store/middleware';
@@ -47,7 +47,7 @@ export class EndpointAlerts {
];
return {
- routes: endpointAlertsRoutes(),
+ SubPluginRoutes: EndpointAlertsRoutes,
store: {
initialState: { alertList: undefined },
/**
diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx
index d62ef20c384d..acc6a82e29a2 100644
--- a/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx
+++ b/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx
@@ -5,12 +5,14 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { AlertIndex } from './view';
-export const endpointAlertsRoutes = () => [
-
-
- ,
-];
+export const EndpointAlertsRoutes: React.FC = () => (
+
+
+
+
+
+);
diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts
new file mode 100644
index 000000000000..0dd66d06b78b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/helpers.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { CoreStart } from '../../../../src/core/public';
+import { APP_ID } from '../common/constants';
+import { SecurityPageName } from './app/types';
+
+export const manageOldSiemRoutes = async (coreStart: CoreStart) => {
+ const { application } = coreStart;
+ const hashPath = window.location.hash.split('?');
+ const search = hashPath.length >= 1 ? hashPath[1] : '';
+ const pageRoute = hashPath.length > 0 ? hashPath[0].split('/') : [];
+ const pageName = pageRoute.length >= 1 ? pageRoute[1] : '';
+ const path = `/${pageRoute.slice(2).join('/') ?? ''}?${search}`;
+
+ switch (pageName) {
+ case SecurityPageName.overview:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, {
+ replace: true,
+ path,
+ });
+ break;
+ case 'ml-hosts':
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ replace: true,
+ path: `/ml-hosts${path}`,
+ });
+ break;
+ case SecurityPageName.hosts:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ replace: true,
+ path,
+ });
+ break;
+ case 'ml-network':
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.network}`, {
+ replace: true,
+ path: `/ml-network${path}`,
+ });
+ break;
+ case SecurityPageName.network:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.network}`, {
+ replace: true,
+ path,
+ });
+ break;
+ case SecurityPageName.timelines:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`, {
+ replace: true,
+ path,
+ });
+ break;
+ case SecurityPageName.case:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ replace: true,
+ path,
+ });
+ break;
+ case 'detections':
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ replace: true,
+ path,
+ });
+ break;
+ default:
+ application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, {
+ replace: true,
+ path: `?${search}`,
+ });
+ break;
+ }
+};
diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
index 1168f4f7454c..1231c35f2146 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
@@ -33,6 +33,8 @@ jest.mock('../../../common/components/query_bar', () => ({
QueryBar: () => null,
}));
+jest.mock('../../../common/components/link_to');
+
describe('Hosts Table', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx
index 7528c148f245..a21d932e837d 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx
@@ -18,6 +18,8 @@ import { mockData } from './mock';
import { HostsType } from '../../store/model';
import * as i18n from './translations';
+jest.mock('../../../common/components/link_to');
+
describe('Uncommon Process Table Component', () => {
const loadPage = jest.fn();
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts
index 90d5f54a027d..d7ee1a53b798 100644
--- a/x-pack/plugins/security_solution/public/hosts/index.ts
+++ b/x-pack/plugins/security_solution/public/hosts/index.ts
@@ -8,7 +8,7 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
import { SecuritySubPluginWithStore } from '../app/types';
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
-import { getHostsRoutes } from './routes';
+import { HostsRoutes } from './routes';
import { initialHostsState, hostsReducer, HostsState } from './store';
const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [
@@ -21,7 +21,7 @@ export class Hosts {
public start(storage: Storage): SecuritySubPluginWithStore<'hosts', HostsState> {
return {
- routes: getHostsRoutes(),
+ SubPluginRoutes: HostsRoutes,
storageTimelines: {
timelineById: getTimelinesInStorageByIds(storage, HOST_TIMELINE_IDS),
},
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
index fa76dc93375e..936789625a4d 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
@@ -71,7 +71,7 @@ describe('body', () => {
test(`it should pass expected object properties to ${componentName}`, () => {
const wrapper = mount(
-
+
(
}}
-
+
>
);
}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx
index 4d04d16580a6..0d30ef32e5d9 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx
@@ -8,10 +8,10 @@ import { omit } from 'lodash/fp';
import * as i18n from '../translations';
import { HostDetailsNavTab } from './types';
import { HostsTableType } from '../../store/model';
-import { SiemPageName } from '../../../app/types';
+import { SecurityPageName } from '../../../app/types';
const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) =>
- `#/${SiemPageName.hosts}/${hostName}/${tabName}`;
+ `/${hostName}/${tabName}`;
export const navTabsHostDetails = (
hostName: string,
@@ -25,6 +25,7 @@ export const navTabsHostDetails = (
disabled: false,
urlKey: 'host',
isDetailPage: true,
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.uncommonProcesses]: {
id: HostsTableType.uncommonProcesses,
@@ -33,6 +34,7 @@ export const navTabsHostDetails = (
disabled: false,
urlKey: 'host',
isDetailPage: true,
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.anomalies]: {
id: HostsTableType.anomalies,
@@ -41,6 +43,7 @@ export const navTabsHostDetails = (
disabled: false,
urlKey: 'host',
isDetailPage: true,
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.events]: {
id: HostsTableType.events,
@@ -49,6 +52,7 @@ export const navTabsHostDetails = (
disabled: false,
urlKey: 'host',
isDetailPage: true,
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.alerts]: {
id: HostsTableType.alerts,
@@ -56,6 +60,7 @@ export const navTabsHostDetails = (
href: getTabsOnHostDetailsUrl(hostName, HostsTableType.alerts),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
};
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
index f145abed2d8f..aa6288d473c9 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
@@ -7,7 +7,6 @@
import { ActionCreator } from 'typescript-fsa';
import { Query, IIndexPattern, Filter } from 'src/plugins/data/public';
import { InputsModelId } from '../../../common/store/inputs/constants';
-import { HostComponentProps } from '../../../common/components/link_to/redirect_to_hosts';
import { HostsTableType } from '../../store/model';
import { HostsQueryProps } from '../types';
import { NavTab } from '../../../common/components/navigation/types';
@@ -40,7 +39,6 @@ export interface HostDetailsProps extends HostsQueryProps {
export type HostDetailsComponentProps = HostDetailsComponentReduxProps &
HostDetailsComponentDispatchProps &
- HostComponentProps &
HostsQueryProps;
type KeyHostDetailsNavTabWithoutMlPermission = HostsTableType.authentications &
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts
index d45cb3368b4e..5c5c7283eee4 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts
@@ -10,13 +10,13 @@ import { get, isEmpty } from 'lodash/fp';
import { ChromeBreadcrumb } from '../../../../../../../src/core/public';
import { hostsModel } from '../../store';
import { HostsTableType } from '../../store/model';
-import {
- getHostsUrl,
- getHostDetailsUrl,
-} from '../../../common/components/link_to/redirect_to_hosts';
+import { getHostDetailsUrl } from '../../../common/components/link_to/redirect_to_hosts';
import * as i18n from '../translations';
import { HostRouteSpyState } from '../../../common/utils/route/types';
+import { GetUrlForApp } from '../../../common/components/navigation/types';
+import { APP_ID } from '../../../../common/constants';
+import { SecurityPageName } from '../../../app/types';
export const type = hostsModel.HostsType.details;
@@ -29,11 +29,17 @@ const TabNameMappedToI18nKey: Record = {
[HostsTableType.alerts]: i18n.NAVIGATION_ALERTS_TITLE,
};
-export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): ChromeBreadcrumb[] => {
+export const getBreadcrumbs = (
+ params: HostRouteSpyState,
+ search: string[],
+ getUrlForApp: GetUrlForApp
+): ChromeBreadcrumb[] => {
let breadcrumb = [
{
text: i18n.PAGE_TITLE,
- href: `${getHostsUrl()}${!isEmpty(search[0]) ? search[0] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: !isEmpty(search[0]) ? search[0] : '',
+ }),
},
];
@@ -42,7 +48,9 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Chr
...breadcrumb,
{
text: params.detailName,
- href: `${getHostDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getHostDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''),
+ }),
},
];
}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
index f7583f65a4fc..f6429544f855 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -8,8 +8,9 @@ import { EuiSpacer } from '@elastic/eui';
import React, { useCallback } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { StickyContainer } from 'react-sticky';
-
import { useParams } from 'react-router-dom';
+
+import { SecurityPageName } from '../../app/types';
import { UpdateDateRange } from '../../common/components/charts/common';
import { FiltersGlobal } from '../../common/components/filters_global';
import { HeaderPage } from '../../common/components/header_page';
@@ -158,7 +159,7 @@ export const HostsComponent = React.memo(
}}
-
+
>
);
}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
index 549c198a4352..e2a0ba0f1fe6 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
@@ -70,22 +70,22 @@ export const HostsTabs = memo(
return (
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx
index 336abc60e5ba..c2285cf0a97e 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx
@@ -5,18 +5,18 @@
*/
import React from 'react';
-import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
+import { Route, Switch, RouteComponentProps, useHistory } from 'react-router-dom';
import { HostDetails } from './details';
import { HostsTableType } from '../store/model';
+import { MlHostConditionalContainer } from '../../common/components/ml/conditional_links/ml_host_conditional_container';
import { GlobalTime } from '../../common/containers/global_time';
-import { SiemPageName } from '../../app/types';
import { Hosts } from './hosts';
import { hostsPagePath, hostDetailsPagePath } from './types';
-const getHostsTabPath = (pagePath: string) =>
- `${pagePath}/:tabName(` +
+const getHostsTabPath = () =>
+ `/:tabName(` +
`${HostsTableType.hosts}|` +
`${HostsTableType.authentications}|` +
`${HostsTableType.uncommonProcesses}|` +
@@ -34,62 +34,75 @@ const getHostDetailsTabPath = (pagePath: string) =>
type Props = Partial> & { url: string };
-export const HostsContainer = React.memo(({ url }) => (
-
- {({ to, from, setQuery, deleteQuery, isInitializing }) => (
-
- (
-
- )}
- />
- (
-
- )}
- />
- }
- />
- (
-
- )}
- />
-
- )}
-
-));
+export const HostsContainer = React.memo(({ url }) => {
+ const history = useHistory();
+ return (
+
+ {({ to, from, setQuery, deleteQuery, isInitializing }) => (
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ {
+ history.replace(`${detailName}/${HostsTableType.authentications}${search}`);
+ return null;
+ }}
+ />
+
+ {
+ history.replace(`${HostsTableType.hosts}${search}`);
+ return null;
+ }}
+ />
+
+ )}
+
+ );
+});
HostsContainer.displayName = 'HostsContainer';
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx
index 9bab3f7efe74..dd1d3b150892 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx
@@ -8,9 +8,9 @@ import { omit } from 'lodash/fp';
import * as i18n from './translations';
import { HostsTableType } from '../store/model';
import { HostsNavTab } from './navigation/types';
-import { SiemPageName } from '../../app/types';
+import { SecurityPageName } from '../../app/types';
-const getTabsOnHostsUrl = (tabName: HostsTableType) => `#/${SiemPageName.hosts}/${tabName}`;
+const getTabsOnHostsUrl = (tabName: HostsTableType) => `/${tabName}`;
export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
const hostsNavTabs = {
@@ -20,6 +20,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.hosts),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.authentications]: {
id: HostsTableType.authentications,
@@ -27,6 +28,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.authentications),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.uncommonProcesses]: {
id: HostsTableType.uncommonProcesses,
@@ -34,6 +36,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.uncommonProcesses),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.anomalies]: {
id: HostsTableType.anomalies,
@@ -41,6 +44,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.anomalies),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.events]: {
id: HostsTableType.events,
@@ -48,6 +52,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.events),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
[HostsTableType.alerts]: {
id: HostsTableType.alerts,
@@ -55,6 +60,7 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => {
href: getTabsOnHostsUrl(HostsTableType.alerts),
disabled: false,
urlKey: 'host',
+ pageId: SecurityPageName.hosts,
},
};
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/types.ts
index 229349f390ec..ffd17b0ef46f 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/pages/types.ts
@@ -7,13 +7,12 @@
import { IIndexPattern } from 'src/plugins/data/public';
import { ActionCreator } from 'typescript-fsa';
-import { SiemPageName } from '../../app/types';
import { hostsModel } from '../store';
import { GlobalTimeArgs } from '../../common/containers/global_time';
import { InputsModelId } from '../../common/store/inputs/constants';
-export const hostsPagePath = `/:pageName(${SiemPageName.hosts})`;
-export const hostDetailsPagePath = `${hostsPagePath}/:detailName`;
+export const hostsPagePath = '/';
+export const hostDetailsPagePath = `/:detailName`;
export type HostsTabsProps = HostsComponentProps & {
filterQuery: string;
diff --git a/x-pack/plugins/security_solution/public/hosts/routes.tsx b/x-pack/plugins/security_solution/public/hosts/routes.tsx
index 93585fa0f839..e6b815cfb503 100644
--- a/x-pack/plugins/security_solution/public/hosts/routes.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/routes.tsx
@@ -5,14 +5,14 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { HostsContainer } from './pages';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getHostsRoutes = () => [
- }
- />,
-];
+export const HostsRoutes = () => (
+
+ } />
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts
index 95293ccc9cf8..7456be1d6784 100644
--- a/x-pack/plugins/security_solution/public/management/common/constants.ts
+++ b/x-pack/plugins/security_solution/public/management/common/constants.ts
@@ -3,11 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { SiemPageName } from '../../app/types';
import { ManagementStoreGlobalNamespace, ManagementSubTab } from '../types';
// --[ ROUTING ]---------------------------------------------------------------------------
-export const MANAGEMENT_ROUTING_ROOT_PATH = `/:pageName(${SiemPageName.management})`;
+export const MANAGEMENT_ROUTING_ROOT_PATH = '';
export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.endpoints})`;
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})/:policyId`;
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts
index bc204535e5ab..92eb7717318d 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.ts
@@ -4,17 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isEmpty } from 'lodash/fp';
import { generatePath } from 'react-router-dom';
// eslint-disable-next-line import/no-nodejs-modules
import querystring from 'querystring';
+
import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
- MANAGEMENT_ROUTING_ROOT_PATH,
} from './constants';
import { ManagementSubTab } from '../types';
-import { SiemPageName } from '../../app/types';
+import { appendSearch } from '../../common/components/link_to/helpers';
import { HostIndexUIQueryParams } from '../pages/endpoint_hosts/types';
// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
@@ -34,76 +35,49 @@ const querystringStringify: (
type EndpointDetailsUrlProps = Omit &
Required>;
-/**
- * Input props for the `getManagementUrl()` method
- */
-export type GetManagementUrlProps = {
- /**
- * Exclude the URL prefix (everything to the left of where the router was mounted.
- * This may be needed when interacting with react-router (ex. to do `history.push()` or
- * validations against matched path)
- */
- excludePrefix?: boolean;
-} & (
- | ({ name: 'default' | 'endpointList' } & HostIndexUIQueryParams)
- | ({ name: 'endpointDetails' | 'endpointPolicyResponse' } & EndpointDetailsUrlProps)
- | { name: 'policyList' }
- | { name: 'policyDetails'; policyId: string }
-);
-
-// Prefix is (almost) everything to the left of where the Router was mounted. In SIEM, since
-// we're using Hash router, thats the `#`.
-const URL_PREFIX = '#';
-
-/**
- * Returns a URL string for a given Management page view
- * @param props
- */
-export const getManagementUrl = (props: GetManagementUrlProps): string => {
- let url = props.excludePrefix ? '' : URL_PREFIX;
-
- if (props.name === 'default' || props.name === 'endpointList') {
- const { name, excludePrefix, ...queryParams } = props;
- const urlQueryParams = querystringStringify(
- queryParams
- );
-
- if (name === 'endpointList') {
- url += generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
- pageName: SiemPageName.management,
- tabName: ManagementSubTab.endpoints,
- });
- } else {
- url += generatePath(MANAGEMENT_ROUTING_ROOT_PATH, {
- pageName: SiemPageName.management,
- });
- }
+export const getEndpointListPath = (
+ props: { name: 'default' | 'endpointList' } & HostIndexUIQueryParams,
+ search?: string
+) => {
+ const { name, ...queryParams } = props;
+ const urlQueryParams = querystringStringify(
+ queryParams
+ );
+ const urlSearch = `${urlQueryParams && !isEmpty(search) ? '&' : ''}${search ?? ''}`;
- if (urlQueryParams) {
- url += `?${urlQueryParams}`;
- }
- } else if (props.name === 'endpointDetails' || props.name === 'endpointPolicyResponse') {
- const { name, excludePrefix, ...queryParams } = props;
- queryParams.show = (props.name === 'endpointPolicyResponse'
- ? 'policy_response'
- : '') as HostIndexUIQueryParams['show'];
-
- url += `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
- pageName: SiemPageName.management,
+ if (name === 'endpointList') {
+ return `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
tabName: ManagementSubTab.endpoints,
- })}?${querystringStringify(queryParams)}`;
- } else if (props.name === 'policyList') {
- url += generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
- pageName: SiemPageName.management,
- tabName: ManagementSubTab.policies,
- });
- } else if (props.name === 'policyDetails') {
- url += generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
- pageName: SiemPageName.management,
- tabName: ManagementSubTab.policies,
- policyId: props.policyId,
- });
+ })}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
}
+ return `${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
+};
- return url;
+export const getEndpointDetailsPath = (
+ props: { name: 'endpointDetails' | 'endpointPolicyResponse' } & EndpointDetailsUrlProps,
+ search?: string
+) => {
+ const { name, ...queryParams } = props;
+ queryParams.show = (props.name === 'endpointPolicyResponse'
+ ? 'policy_response'
+ : '') as HostIndexUIQueryParams['show'];
+ const urlQueryParams = querystringStringify(
+ queryParams
+ );
+ const urlSearch = `${urlQueryParams && !isEmpty(search) ? '&' : ''}${search ?? ''}`;
+
+ return `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
+ tabName: ManagementSubTab.endpoints,
+ })}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
};
+
+export const getPoliciesPath = (search?: string) =>
+ `${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
+ tabName: ManagementSubTab.policies,
+ })}${appendSearch(search)}`;
+
+export const getPolicyDetailPath = (policyId: string, search?: string) =>
+ `${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
+ tabName: ManagementSubTab.policies,
+ policyId,
+ })}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
index 8f7e0e06fb7a..c3dbb93b369a 100644
--- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
@@ -9,10 +9,21 @@ import { i18n } from '@kbn/i18n';
import { useParams } from 'react-router-dom';
import { PageView, PageViewProps } from '../../common/components/endpoint/page_view';
import { ManagementSubTab } from '../types';
-import { getManagementUrl } from '..';
+import { SecurityPageName } from '../../app/types';
+import { useFormatUrl } from '../../common/components/link_to';
+import { getEndpointListPath, getPoliciesPath } from '../common/routing';
+import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler';
export const ManagementPageView = memo>((options) => {
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const { tabName } = useParams<{ tabName: ManagementSubTab }>();
+
+ const goToEndpoint = useNavigateByRouterEventHandler(
+ getEndpointListPath({ name: 'endpointList' }, search)
+ );
+
+ const goToPolicies = useNavigateByRouterEventHandler(getPoliciesPath(search));
+
const tabs = useMemo((): PageViewProps['tabs'] | undefined => {
if (options.viewType === 'details') {
return undefined;
@@ -24,7 +35,8 @@ export const ManagementPageView = memo>((options) =>
}),
id: ManagementSubTab.endpoints,
isSelected: tabName === ManagementSubTab.endpoints,
- href: getManagementUrl({ name: 'endpointList' }),
+ href: formatUrl(getEndpointListPath({ name: 'endpointList' })),
+ onClick: goToEndpoint,
},
{
name: i18n.translate('xpack.securitySolution.managementTabs.policies', {
@@ -32,10 +44,11 @@ export const ManagementPageView = memo>((options) =>
}),
id: ManagementSubTab.policies,
isSelected: tabName === ManagementSubTab.policies,
- href: getManagementUrl({ name: 'policyList' }),
+ href: formatUrl(getPoliciesPath()),
+ onClick: goToPolicies,
},
];
- }, [options.viewType, tabName]);
+ }, [formatUrl, goToEndpoint, goToPolicies, options.viewType, tabName]);
return ;
});
diff --git a/x-pack/plugins/security_solution/public/management/index.ts b/x-pack/plugins/security_solution/public/management/index.ts
index d6a723e5340b..902ed085bd36 100644
--- a/x-pack/plugins/security_solution/public/management/index.ts
+++ b/x-pack/plugins/security_solution/public/management/index.ts
@@ -6,7 +6,7 @@
import { CoreStart } from 'kibana/public';
import { Reducer, CombinedState } from 'redux';
-import { managementRoutes } from './routes';
+import { ManagementRoutes } from './routes';
import { StartPlugins } from '../types';
import { SecuritySubPluginWithStore } from '../app/types';
import { managementReducer } from './store/reducer';
@@ -14,8 +14,6 @@ import { AppAction } from '../common/store/actions';
import { managementMiddlewareFactory } from './store/middleware';
import { ManagementState } from './types';
-export { getManagementUrl } from './common/routing';
-
/**
* Internally, our state is sometimes immutable, ignore that in our external
* interface.
@@ -40,7 +38,7 @@ export class Management {
plugins: StartPlugins
): SecuritySubPluginWithStore<'management', ManagementState> {
return {
- routes: managementRoutes(),
+ SubPluginRoutes: ManagementRoutes,
store: {
initialState: {
management: undefined,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
index 4ff9ecfaeab0..3f92358b93d5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
@@ -5,12 +5,14 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { HostList } from './view';
-export const endpointHostsRoutes = () => [
-
-
- ,
-];
+export const EndpointHostsRoutes: React.FC = () => (
+
+
+
+
+
+);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
index b8eaa39c7775..ae2ce9facc83 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
@@ -24,7 +24,7 @@ import {
MiddlewareActionSpyHelper,
createSpyMiddleware,
} from '../../../../common/store/test_utils';
-import { getManagementUrl } from '../../..';
+import { getEndpointListPath } from '../../../common/routing';
describe('host list pagination: ', () => {
let fakeCoreStart: jest.Mocked;
@@ -56,9 +56,7 @@ describe('host list pagination: ', () => {
queryParams = () => uiQueryParams(store.getState());
historyPush = (nextQueryParams: HostIndexUIQueryParams): void => {
- return history.push(
- getManagementUrl({ name: 'endpointList', excludePrefix: true, ...nextQueryParams })
- );
+ return history.push(getEndpointListPath({ name: 'endpointList', ...nextQueryParams }));
};
});
@@ -72,7 +70,7 @@ describe('host list pagination: ', () => {
type: 'userChangedUrl',
payload: {
...history.location,
- pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
+ pathname: getEndpointListPath({ name: 'endpointList' }),
},
});
await waitForAction('serverReturnedHostList');
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
index a6cd2ca3afac..e62c53e061a3 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
@@ -21,7 +21,7 @@ import { listData } from './selectors';
import { HostState } from '../types';
import { hostListReducer } from './reducer';
import { hostMiddlewareFactory } from './middleware';
-import { getManagementUrl } from '../../..';
+import { getEndpointListPath } from '../../../common/routing';
describe('host list middleware', () => {
let fakeCoreStart: jest.Mocked;
@@ -60,7 +60,7 @@ describe('host list middleware', () => {
type: 'userChangedUrl',
payload: {
...history.location,
- pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
+ pathname: getEndpointListPath({ name: 'endpointList' }),
},
});
await waitForAction('serverReturnedHostList');
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
index f31b54b93851..b7e90c19799c 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx
@@ -25,7 +25,9 @@ import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants';
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
-import { getManagementUrl } from '../../../..';
+import { getEndpointDetailsPath, getPolicyDetailPath } from '../../../../common/routing';
+import { SecurityPageName } from '../../../../../app/types';
+import { useFormatUrl } from '../../../../../common/components/link_to';
const HostIds = styled(EuiListGroupItem)`
margin-top: 0;
@@ -51,6 +53,8 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
const policyStatus = useHostSelector(
policyResponseStatus
) as keyof typeof POLICY_STATUS_TO_HEALTH_COLOR;
+ const { formatUrl } = useFormatUrl(SecurityPageName.management);
+
const detailsResultsUpper = useMemo(() => {
return [
{
@@ -77,35 +81,29 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
const { selected_host, show, ...currentUrlParams } = queryParams;
return [
- getManagementUrl({
+ formatUrl(
+ getEndpointDetailsPath({
+ name: 'endpointPolicyResponse',
+ ...currentUrlParams,
+ selected_host: details.host.id,
+ })
+ ),
+ getEndpointDetailsPath({
name: 'endpointPolicyResponse',
...currentUrlParams,
selected_host: details.host.id,
}),
- getManagementUrl({
- name: 'endpointPolicyResponse',
- excludePrefix: true,
- ...currentUrlParams,
- selected_host: details.host.id,
- }),
];
- }, [details.host.id, queryParams]);
+ }, [details.host.id, formatUrl, queryParams]);
const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath);
const [policyDetailsRoutePath, policyDetailsRouteUrl] = useMemo(() => {
return [
- getManagementUrl({
- name: 'policyDetails',
- policyId: details.Endpoint.policy.applied.id,
- excludePrefix: true,
- }),
- getManagementUrl({
- name: 'policyDetails',
- policyId: details.Endpoint.policy.applied.id,
- }),
+ getPolicyDetailPath(details.Endpoint.policy.applied.id),
+ formatUrl(getPolicyDetailPath(details.Endpoint.policy.applied.id)),
];
- }, [details.Endpoint.policy.applied.id]);
+ }, [details.Endpoint.policy.applied.id, formatUrl]);
const policyDetailsClickHandler = useNavigateByRouterEventHandler(policyDetailsRoutePath);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
index ed853e24d7c3..3d44b73858e9 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
@@ -38,7 +38,9 @@ import { PolicyResponse } from './policy_response';
import { HostMetadata } from '../../../../../../common/endpoint/types';
import { FlyoutSubHeader, FlyoutSubHeaderProps } from './components/flyout_sub_header';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
-import { getManagementUrl } from '../../../..';
+import { getEndpointListPath } from '../../../../common/routing';
+import { SecurityPageName } from '../../../../../app/types';
+import { useFormatUrl } from '../../../../../common/components/link_to';
export const HostDetailsFlyout = memo(() => {
const history = useHistory();
@@ -116,21 +118,23 @@ const PolicyResponseFlyoutPanel = memo<{
const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount);
const loading = useHostSelector(policyResponseLoading);
const error = useHostSelector(policyResponseError);
+ const { formatUrl } = useFormatUrl(SecurityPageName.management);
const [detailsUri, detailsRoutePath] = useMemo(
() => [
- getManagementUrl({
+ formatUrl(
+ getEndpointListPath({
+ name: 'endpointList',
+ ...queryParams,
+ selected_host: hostMeta.host.id,
+ })
+ ),
+ getEndpointListPath({
name: 'endpointList',
...queryParams,
selected_host: hostMeta.host.id,
}),
- getManagementUrl({
- name: 'endpointList',
- excludePrefix: true,
- ...queryParams,
- selected_host: hostMeta.host.id,
- }),
],
- [hostMeta.host.id, queryParams]
+ [hostMeta.host.id, formatUrl, queryParams]
);
const backToDetailsClickHandler = useNavigateByRouterEventHandler(detailsRoutePath);
const backButtonProp = useMemo((): FlyoutSubHeaderProps['backButton'] => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index a3e2a55fc756..224411c5f7ec 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -252,7 +252,7 @@ describe('when on the hosts page', () => {
const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue');
expect(policyDetailsLink).not.toBeNull();
expect(policyDetailsLink.getAttribute('href')).toEqual(
- `#/management/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
+ `/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
);
});
@@ -268,7 +268,7 @@ describe('when on the hosts page', () => {
});
const changedUrlAction = await userChangedUrlChecker;
expect(changedUrlAction.payload.pathname).toEqual(
- `/management/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
+ `/policy/${hostDetails.metadata.Endpoint.policy.applied.id}`
);
});
@@ -277,7 +277,7 @@ describe('when on the hosts page', () => {
const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
expect(policyStatusLink).not.toBeNull();
expect(policyStatusLink.getAttribute('href')).toEqual(
- '#/management/endpoints?page_index=0&page_size=10&selected_host=1&show=policy_response'
+ '/endpoints?page_index=0&page_size=10&selected_host=1&show=policy_response'
);
});
@@ -489,7 +489,7 @@ describe('when on the hosts page', () => {
const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton');
expect(subHeaderBackLink.textContent).toBe('Endpoint Details');
expect(subHeaderBackLink.getAttribute('href')).toBe(
- '#/management/endpoints?page_index=0&page_size=10&selected_host=1'
+ '/endpoints?page_index=0&page_size=10&selected_host=1'
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index 149819480855..45a33f76ee0c 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -32,8 +32,14 @@ import { CreateStructuredSelector } from '../../../../common/store';
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { ManagementPageView } from '../../../components/management_page_view';
-import { getManagementUrl } from '../../..';
import { FormattedDate } from '../../../../common/components/formatted_date';
+import { SecurityPageName } from '../../../../app/types';
+import {
+ getEndpointListPath,
+ getEndpointDetailsPath,
+ getPolicyDetailPath,
+} from '../../../common/routing';
+import { useFormatUrl } from '../../../../common/components/link_to';
const HostListNavLink = memo<{
name: string;
@@ -70,6 +76,7 @@ export const HostList = () => {
uiQueryParams: queryParams,
hasSelectedHost,
} = useHostSelector(selector);
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const paginationSetup = useMemo(() => {
return {
@@ -86,9 +93,8 @@ export const HostList = () => {
const { index, size } = page;
// FIXME: PT: if host details is open, table is not displaying correct number of rows
history.push(
- getManagementUrl({
+ getEndpointListPath({
name: 'endpointList',
- excludePrefix: true,
...queryParams,
page_index: JSON.stringify(index),
page_size: JSON.stringify(size),
@@ -110,17 +116,15 @@ export const HostList = () => {
defaultMessage: 'Hostname',
}),
render: ({ hostname, id }: HostInfo['metadata']['host']) => {
- const toRoutePath = getManagementUrl({
- ...queryParams,
- name: 'endpointDetails',
- selected_host: id,
- excludePrefix: true,
- });
- const toRouteUrl = getManagementUrl({
- ...queryParams,
- name: 'endpointDetails',
- selected_host: id,
- });
+ const toRoutePath = getEndpointDetailsPath(
+ {
+ ...queryParams,
+ name: 'endpointDetails',
+ selected_host: id,
+ },
+ search
+ );
+ const toRouteUrl = formatUrl(toRoutePath);
return (
{
truncateText: true,
// eslint-disable-next-line react/display-name
render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied']) => {
- const toRoutePath = getManagementUrl({
- name: 'policyDetails',
- policyId: policy.id,
- excludePrefix: true,
- });
- const toRouteUrl = getManagementUrl({
- name: 'policyDetails',
- policyId: policy.id,
- });
+ const toRoutePath = getPolicyDetailPath(policy.id);
+ const toRouteUrl = formatUrl(toRoutePath);
return (
{
}),
// eslint-disable-next-line react/display-name
render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied'], item: HostInfo) => {
- const toRoutePath = getManagementUrl({
- name: 'endpointPolicyResponse',
- selected_host: item.metadata.host.id,
- excludePrefix: true,
- });
- const toRouteUrl = getManagementUrl({
+ const toRoutePath = getEndpointDetailsPath({
name: 'endpointPolicyResponse',
selected_host: item.metadata.host.id,
});
+ const toRouteUrl = formatUrl(toRoutePath);
return (
{
},
},
];
- }, [queryParams]);
+ }, [formatUrl, queryParams, search]);
return (
{
pagination={paginationSetup}
onChange={onTableChange}
/>
-
+
);
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx
index 588b26776323..0e81b75d651b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx
@@ -5,7 +5,8 @@
*/
import React, { memo } from 'react';
-import { Redirect, Route, Switch } from 'react-router-dom';
+import { useHistory, Route, Switch } from 'react-router-dom';
+
import { PolicyContainer } from './policy';
import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
@@ -14,9 +15,10 @@ import {
} from '../common/constants';
import { NotFoundPage } from '../../app/404';
import { EndpointsContainer } from './endpoint_hosts';
-import { getManagementUrl } from '..';
+import { getEndpointListPath } from '../common/routing';
export const ManagementContainer = memo(() => {
+ const history = useHistory();
return (
@@ -24,9 +26,10 @@ export const ManagementContainer = memo(() => {
(
-
- )}
+ render={() => {
+ history.replace(getEndpointListPath({ name: 'endpointList' }));
+ return null;
+ }}
/>
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts
index ce81c58893a7..c24c47becc0b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts
@@ -26,10 +26,10 @@ import {
createSpyMiddleware,
MiddlewareActionSpyHelper,
} from '../../../../../common/store/test_utils';
-import { getManagementUrl } from '../../../../common/routing';
+import { getPoliciesPath } from '../../../../common/routing';
describe('policy list store concerns', () => {
- const policyListPathUrl = getManagementUrl({ name: 'policyList', excludePrefix: true });
+ const policyListPathUrl = getPoliciesPath();
let fakeCoreStart: ReturnType;
let depsStart: DepsStartMock;
let store: Store;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx
index 5e721cadfa82..20346cb720ac 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx
@@ -13,7 +13,7 @@ import {
CustomConfigureDatasourceContent,
CustomConfigureDatasourceProps,
} from '../../../../../../../ingest_manager/public';
-import { getManagementUrl } from '../../../..';
+import { getPolicyDetailPath } from '../../../../common/routing';
/**
* Exports Endpoint-specific datasource configuration instructions
@@ -24,10 +24,7 @@ export const ConfigureEndpointDatasource = memo {
type FindReactWrapperResponse = ReturnType['find']>;
- const policyDetailsPathUrl = getManagementUrl({
- name: 'policyDetails',
- policyId: '1',
- excludePrefix: true,
- });
- const policyListPathUrl = getManagementUrl({ name: 'policyList', excludePrefix: true });
- const policyListPathUrlWithPrefix = getManagementUrl({ name: 'policyList' });
+ const policyDetailsPathUrl = getPolicyDetailPath('1');
+ const policyListPathUrl = getPoliciesPath();
const sleep = (ms = 100) => new Promise((wakeup) => setTimeout(wakeup, ms));
const generator = new EndpointDocGenerator();
const { history, AppWrapper, coreStart } = createAppRootMockRenderer();
@@ -97,7 +92,7 @@ describe('Policy Details', () => {
const backToListButton = pageHeaderLeft.find('EuiButtonEmpty');
expect(backToListButton.prop('iconType')).toBe('arrowLeft');
- expect(backToListButton.prop('href')).toBe(policyListPathUrlWithPrefix);
+ expect(backToListButton.prop('href')).toBe(policyListPathUrl);
expect(backToListButton.text()).toBe('Back to policy list');
const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]');
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
index d1f7da91bd6f..6a48ae735180 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
@@ -38,11 +38,14 @@ import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoi
import { PageViewHeaderTitle } from '../../../../common/components/endpoint/page_view';
import { ManagementPageView } from '../../../components/management_page_view';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
-import { getManagementUrl } from '../../../common/routing';
+import { SecurityPageName } from '../../../../app/types';
+import { getPoliciesPath } from '../../../common/routing';
+import { useFormatUrl } from '../../../../common/components/link_to';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
const { notifications } = useKibana();
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
// Store values
const policyItem = usePolicyDetailsSelector(policyDetails);
@@ -89,9 +92,7 @@ export const PolicyDetails = React.memo(() => {
}
}, [notifications.toasts, policyName, policyUpdateStatus]);
- const handleBackToListOnClick = useNavigateByRouterEventHandler(
- getManagementUrl({ name: 'policyList', excludePrefix: true })
- );
+ const handleBackToListOnClick = useNavigateByRouterEventHandler(getPoliciesPath());
const handleSaveOnClick = useCallback(() => {
setShowConfirm(true);
@@ -121,7 +122,7 @@ export const PolicyDetails = React.memo(() => {
{policyApiError?.message}
) : null}
-
+
);
}
@@ -133,7 +134,7 @@ export const PolicyDetails = React.memo(() => {
iconType="arrowLeft"
contentProps={{ style: { paddingLeft: '0' } }}
onClick={handleBackToListOnClick}
- href={getManagementUrl({ name: 'policyList' })}
+ href={formatUrl(getPoliciesPath(search))}
>
{
-
+
>
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
index 63a5cd0d67ae..acce5c8f7835 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
@@ -39,7 +39,7 @@ describe('when on the policies page', () => {
let firstPolicyID: string;
beforeEach(() => {
reactTestingLibrary.act(() => {
- history.push('/management/policy');
+ history.push('/policy');
reactTestingLibrary.act(() => {
const policyListData = mockPolicyResultList({ total: 3 });
firstPolicyID = policyListData.items[0].id;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
index 2d4abd6de0e4..4532408332d6 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
@@ -42,8 +42,10 @@ import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoi
import { LinkToApp } from '../../../../common/components/endpoint/link_to_app';
import { ManagementPageView } from '../../../components/management_page_view';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
-import { getManagementUrl } from '../../../common/routing';
import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time';
+import { SecurityPageName } from '../../../../app/types';
+import { useFormatUrl } from '../../../../common/components/link_to';
+import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { CreateDatasourceRouteState } from '../../../../../../ingest_manager/public';
import { useEndpointPackageInfo } from './ingest_hooks';
@@ -127,6 +129,7 @@ export const PolicyList = React.memo(() => {
const { services, notifications } = useKibana();
const history = useHistory();
const location = useLocation();
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const [showDelete, setShowDelete] = useState(false);
const [policyIdToDelete, setPolicyIdToDelete] = useState('');
@@ -155,14 +158,9 @@ export const PolicyList = React.memo(() => {
// of the cancel and submit buttons and redirect the user back to endpoint policy
path: `#/integrations${packageInfo ? `/endpoint-${packageInfo.version}/add-datasource` : ''}`,
state: {
- onCancelNavigateTo: [
- 'securitySolution',
- { path: getManagementUrl({ name: 'policyList' }) },
- ],
- onCancelUrl: services.application?.getUrlForApp('securitySolution', {
- path: getManagementUrl({ name: 'policyList' }),
- }),
- onSaveNavigateTo: ['securitySolution', { path: getManagementUrl({ name: 'policyList' }) }],
+ onCancelNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }],
+ onCancelUrl: formatUrl(getPoliciesPath()),
+ onSaveNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }],
},
}
);
@@ -265,12 +263,8 @@ export const PolicyList = React.memo(() => {
}),
// eslint-disable-next-line react/display-name
render: (name: string, item: Immutable) => {
- const routePath = getManagementUrl({
- name: 'policyDetails',
- policyId: item.id,
- excludePrefix: true,
- });
- const routeUrl = getManagementUrl({ name: 'policyDetails', policyId: item.id });
+ const routePath = getPolicyDetailPath(item.id, search);
+ const routeUrl = formatUrl(routePath);
return (
@@ -382,7 +376,7 @@ export const PolicyList = React.memo(() => {
],
},
],
- [services.application, handleDeleteOnClick]
+ [services.application, handleDeleteOnClick, formatUrl, search]
);
return (
@@ -461,7 +455,7 @@ export const PolicyList = React.memo(() => {
handleTableChange,
paginationSetup,
])}
-
+
>
);
diff --git a/x-pack/plugins/security_solution/public/management/routes.tsx b/x-pack/plugins/security_solution/public/management/routes.tsx
index 12727ea97458..209d7dd6dbcd 100644
--- a/x-pack/plugins/security_solution/public/management/routes.tsx
+++ b/x-pack/plugins/security_solution/public/management/routes.tsx
@@ -5,14 +5,16 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { ManagementContainer } from './pages';
-import { MANAGEMENT_ROUTING_ROOT_PATH } from './common/constants';
+import { NotFoundPage } from '../app/404';
/**
* Returns the React Router Routes for the management area
*/
-export const managementRoutes = () => [
- // Mounts the Management interface on `/management`
- ,
-];
+export const ManagementRoutes = () => (
+
+
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts
index fd6b9f6e6a82..854e9faa0204 100644
--- a/x-pack/plugins/security_solution/public/management/types.ts
+++ b/x-pack/plugins/security_solution/public/management/types.ts
@@ -5,7 +5,7 @@
*/
import { CombinedState } from 'redux';
-import { SiemPageName } from '../app/types';
+import { SecurityPageName } from '../app/types';
import { PolicyListState, PolicyDetailsState } from './pages/policy/types';
import { HostState } from './pages/endpoint_hosts/types';
@@ -33,7 +33,7 @@ export enum ManagementSubTab {
* The URL route params for the Management Policy List section
*/
export interface ManagementRoutePolicyListParams {
- pageName: SiemPageName.management;
+ pageName: SecurityPageName.management;
tabName: ManagementSubTab.policies;
}
diff --git a/x-pack/plugins/security_solution/public/network/components/ip/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/ip/index.test.tsx
index 8d53e75b81af..d94961026488 100644
--- a/x-pack/plugins/security_solution/public/network/components/ip/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/ip/index.test.tsx
@@ -12,6 +12,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { Ip } from '.';
+jest.mock('../../../common/components/link_to');
+
describe('Port', () => {
const mount = useMountAppended();
@@ -42,6 +44,6 @@ describe('Port', () => {
expect(
wrapper.find('[data-test-subj="draggable-content-destination.ip"]').find('a').first().props()
.href
- ).toEqual('#/link-to/network/ip/10.1.2.3/source');
+ ).toEqual('/ip/10.1.2.3/source');
});
});
diff --git a/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx
index 39fad58ca352..ac37aaf30915 100644
--- a/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx
@@ -24,6 +24,8 @@ import { networkModel } from '../../store';
import { NetworkHttpTable } from '.';
import { mockData } from './mock';
+jest.mock('../../../common/components/link_to');
+
describe('NetworkHttp Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx
index 4de04f673d87..b14d411810de 100644
--- a/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx
@@ -24,6 +24,8 @@ import { networkModel } from '../../store';
import { NetworkTopNFlowTable } from '.';
import { mockData } from './mock';
+jest.mock('../../../common/components/link_to');
+
describe('NetworkTopNFlow Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
index 3016e34dfd73..cefe2821b524 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
@@ -95,6 +95,14 @@ const getSourceDestinationInstance = () => (
/>
);
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ createHref: jest.fn(),
+ push: jest.fn(),
+ }),
+}));
+
describe('SourceDestination', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx
index 3c1668bbb341..d9d926fb52d1 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx
@@ -35,6 +35,8 @@ import {
SOURCE_GEO_REGION_NAME_FIELD_NAME,
} from './geo_fields';
+jest.mock('../../../common/components/link_to');
+
describe('SourceDestinationIp', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/network/index.ts b/x-pack/plugins/security_solution/public/network/index.ts
index 63291ad2d239..bd8148f339d6 100644
--- a/x-pack/plugins/security_solution/public/network/index.ts
+++ b/x-pack/plugins/security_solution/public/network/index.ts
@@ -6,7 +6,7 @@
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { SecuritySubPluginWithStore } from '../app/types';
-import { getNetworkRoutes } from './routes';
+import { NetworkRoutes } from './routes';
import { initialNetworkState, networkReducer, NetworkState } from './store';
import { TimelineId } from '../../common/types/timeline';
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
@@ -16,7 +16,7 @@ export class Network {
public start(storage: Storage): SecuritySubPluginWithStore<'network', NetworkState> {
return {
- routes: getNetworkRoutes(),
+ SubPluginRoutes: NetworkRoutes,
storageTimelines: {
timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]),
},
diff --git a/x-pack/plugins/security_solution/public/network/pages/index.tsx b/x-pack/plugins/security_solution/public/network/pages/index.tsx
index c6f13c118c30..c7a8a5f705df 100644
--- a/x-pack/plugins/security_solution/public/network/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/index.tsx
@@ -5,7 +5,7 @@
*/
import React, { useMemo } from 'react';
-import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
+import { Route, Switch, RouteComponentProps, useHistory } from 'react-router-dom';
import { useMlCapabilities } from '../../common/components/ml_popover/hooks/use_ml_capabilities';
import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions';
@@ -14,23 +14,24 @@ import { FlowTarget } from '../../graphql/types';
import { IPDetails } from './ip_details';
import { Network } from './network';
import { GlobalTime } from '../../common/containers/global_time';
-import { SiemPageName } from '../../app/types';
import { getNetworkRoutePath } from './navigation';
import { NetworkRouteType } from './navigation/types';
+import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container';
type Props = Partial> & { url: string };
-const networkPagePath = `/:pageName(${SiemPageName.network})`;
-const ipDetailsPageBasePath = `${networkPagePath}/ip/:detailName`;
+const networkPagePath = '';
+const ipDetailsPageBasePath = `/ip/:detailName`;
const NetworkContainerComponent: React.FC = () => {
+ const history = useHistory();
const capabilities = useMlCapabilities();
const capabilitiesFetched = capabilities.capabilitiesFetched;
const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [
capabilities,
]);
const networkRoutePath = useMemo(
- () => getNetworkRoutePath(networkPagePath, capabilitiesFetched, userHasMlUserPermissions),
+ () => getNetworkRoutePath(capabilitiesFetched, userHasMlUserPermissions),
[capabilitiesFetched, userHasMlUserPermissions]
);
@@ -38,6 +39,12 @@ const NetworkContainerComponent: React.FC = () => {
{({ to, from, setQuery, deleteQuery, isInitializing }) => (
+ (
+
+ )}
+ />
= () => {
match: {
params: { detailName },
},
- }) => (
-
- )}
+ }) => {
+ history.replace(`ip/${detailName}/${FlowTarget.source}${search}`);
+ return null;
+ }}
/>
(
-
- )}
+ path="/"
+ render={({ location: { search = '' } }) => {
+ history.replace(`${NetworkRouteType.flows}${search}`);
+ return null;
+ }}
/>
)}
diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap
index e7598ef03d78..6e76ff00a814 100644
--- a/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap
@@ -8,6 +8,8 @@ exports[`Ip Details it matches the snapshot 1`] = `
>
-
+
`;
diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx
index 9ae09d6c6cec..face3f890479 100644
--- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx
@@ -45,6 +45,7 @@ import { UsersQueryTable } from './users_query_table';
import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body';
import { esQuery } from '../../../../../../../src/plugins/data/public';
import { networkModel } from '../../store';
+import { SecurityPageName } from '../../../app/types';
export { getBreadcrumbs } from './utils';
const IpOverviewManage = manageQuery(IpOverview);
@@ -273,7 +274,7 @@ export const IPDetailsComponent: React.FC
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/utils.ts b/x-pack/plugins/security_solution/public/network/pages/ip_details/utils.ts
index b1f986f20778..640b9d9818cd 100644
--- a/x-pack/plugins/security_solution/public/network/pages/ip_details/utils.ts
+++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/utils.ts
@@ -9,14 +9,14 @@ import { get, isEmpty } from 'lodash/fp';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ChromeBreadcrumb } from '../../../../../../../src/core/public';
import { decodeIpv6 } from '../../../common/lib/helpers';
-import {
- getNetworkUrl,
- getIPDetailsUrl,
-} from '../../../common/components/link_to/redirect_to_network';
+import { getIPDetailsUrl } from '../../../common/components/link_to/redirect_to_network';
import { networkModel } from '../../store';
import * as i18n from '../translations';
import { NetworkRouteType } from '../navigation/types';
import { NetworkRouteSpyState } from '../../../common/utils/route/types';
+import { GetUrlForApp } from '../../../common/components/navigation/types';
+import { APP_ID } from '../../../../common/constants';
+import { SecurityPageName } from '../../../app/types';
export const type = networkModel.NetworkType.details;
const TabNameMappedToI18nKey: Record = {
@@ -30,12 +30,15 @@ const TabNameMappedToI18nKey: Record = {
export const getBreadcrumbs = (
params: NetworkRouteSpyState,
- search: string[]
+ search: string[],
+ getUrlForApp: GetUrlForApp
): ChromeBreadcrumb[] => {
let breadcrumb = [
{
text: i18n.PAGE_TITLE,
- href: `${getNetworkUrl()}${!isEmpty(search[0]) ? search[0] : ''}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.network}`, {
+ path: !isEmpty(search[0]) ? search[0] : '',
+ }),
},
];
if (params.detailName != null) {
@@ -43,9 +46,13 @@ export const getBreadcrumbs = (
...breadcrumb,
{
text: decodeIpv6(params.detailName),
- href: `${getIPDetailsUrl(params.detailName, params.flowTarget)}${
- !isEmpty(search[1]) ? search[1] : ''
- }`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.network}`, {
+ path: getIPDetailsUrl(
+ params.detailName,
+ params.flowTarget,
+ !isEmpty(search[0]) ? search[0] : ''
+ ),
+ }),
},
];
}
diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx
index 61f1a5aacb9c..a563d44012ea 100644
--- a/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx
@@ -7,8 +7,9 @@
import { omit } from 'lodash/fp';
import * as i18n from '../translations';
import { NetworkNavTab, NetworkRouteType } from './types';
+import { SecurityPageName } from '../../../app/types';
-const getTabsOnNetworkUrl = (tabName: NetworkRouteType) => `#/network/${tabName}`;
+const getTabsOnNetworkUrl = (tabName: NetworkRouteType) => `/${tabName}`;
export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab => {
const networkNavTabs = {
@@ -18,6 +19,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.flows),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
[NetworkRouteType.dns]: {
id: NetworkRouteType.dns,
@@ -25,6 +27,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.dns),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
[NetworkRouteType.http]: {
id: NetworkRouteType.http,
@@ -32,6 +35,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.http),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
[NetworkRouteType.tls]: {
id: NetworkRouteType.tls,
@@ -39,6 +43,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.tls),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
[NetworkRouteType.anomalies]: {
id: NetworkRouteType.anomalies,
@@ -46,6 +51,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.anomalies),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
[NetworkRouteType.alerts]: {
id: NetworkRouteType.alerts,
@@ -53,6 +59,7 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.alerts),
disabled: false,
urlKey: 'network',
+ pageId: SecurityPageName.network,
},
};
diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx
index 08ed0d9769be..e1e84b3ef427 100644
--- a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx
@@ -100,10 +100,10 @@ export const NetworkRoutes = React.memo(
return (
-
+
-
+
<>
@@ -129,19 +129,19 @@ export const NetworkRoutes = React.memo(
>
-
+
-
+
-
+
-
+
diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts
index 0f48aad57b3a..433ed7fffd74 100644
--- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts
@@ -67,11 +67,10 @@ export enum NetworkRouteType {
anomalies = 'anomalies',
tls = 'tls',
http = 'http',
- alerts = 'alerts',
+ alerts = 'external-alerts',
}
export type GetNetworkRoutePath = (
- pagePath: string,
capabilitiesFetched: boolean,
hasMlUserPermission: boolean
) => string;
diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts
index 24c2011fd380..25a91db4eef9 100644
--- a/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts
+++ b/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts
@@ -7,16 +7,15 @@
import { GetNetworkRoutePath, NetworkRouteType } from './types';
export const getNetworkRoutePath: GetNetworkRoutePath = (
- pagePath,
capabilitiesFetched,
hasMlUserPermission
) => {
if (capabilitiesFetched && !hasMlUserPermission) {
- return `${pagePath}/:tabName(${NetworkRouteType.flows}|${NetworkRouteType.dns}|${NetworkRouteType.http}|${NetworkRouteType.tls}|${NetworkRouteType.alerts})`;
+ return `/:tabName(${NetworkRouteType.flows}|${NetworkRouteType.dns}|${NetworkRouteType.http}|${NetworkRouteType.tls}|${NetworkRouteType.alerts})`;
}
return (
- `${pagePath}/:tabName(` +
+ `/:tabName(` +
`${NetworkRouteType.flows}|` +
`${NetworkRouteType.dns}|` +
`${NetworkRouteType.anomalies}|` +
diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx
index 2f7a97ed3d19..845a6bbd95dd 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx
@@ -11,6 +11,7 @@ import { useParams } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';
import { esQuery } from '../../../../../../src/plugins/data/public';
+import { SecurityPageName } from '../../app/types';
import { UpdateDateRange } from '../../common/components/charts/common';
import { EmbeddedMap } from '../components/embeddables/embedded_map';
import { FiltersGlobal } from '../../common/components/filters_global';
@@ -175,7 +176,7 @@ const NetworkComponent = React.memo(
}}
-
+
>
);
}
diff --git a/x-pack/plugins/security_solution/public/network/routes.tsx b/x-pack/plugins/security_solution/public/network/routes.tsx
index 6f3fd28ec53b..d6d725512bdb 100644
--- a/x-pack/plugins/security_solution/public/network/routes.tsx
+++ b/x-pack/plugins/security_solution/public/network/routes.tsx
@@ -5,14 +5,17 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { NetworkContainer } from './pages';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getNetworkRoutes = () => [
- }
- />,
-];
+export const NetworkRoutes = () => (
+
+ }
+ />
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx
index ba82c7f92855..1aa114608b47 100644
--- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx
@@ -17,8 +17,8 @@ import { mockIndexPattern, TestProviders } from '../../../common/mock';
import { AlertsByCategory } from '.';
+jest.mock('../../../common/components/link_to');
jest.mock('../../../common/lib/kibana');
-
jest.mock('../../../common/containers/matrix_histogram', () => {
return {
useQuery: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
index fe66ae2624c2..03e8279f01db 100644
--- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
@@ -4,12 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButton } from '@elastic/eui';
import numeral from '@elastic/numeral';
-import React, { useEffect, useMemo } from 'react';
+import React, { useEffect, useMemo, useCallback } from 'react';
import { Position } from '@elastic/charts';
-import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
+import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants';
import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations';
import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram';
import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
@@ -29,9 +28,10 @@ import {
histogramConfigs,
} from '../../../common/components/alerts_viewer/histogram_configs';
import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts';
+import { SecurityPageName } from '../../../app/types';
+import { useFormatUrl } from '../../../common/components/link_to';
+import { LinkButton } from '../../../common/components/links';
const ID = 'alertsByCategoryOverview';
@@ -75,19 +75,31 @@ const AlertsByCategoryComponent: React.FC = ({
}, []);
const kibana = useKibana();
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.hosts);
+ const { navigateToApp } = kibana.services.application;
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
- const urlSearch = useGetUrlSearch(navTabs.hosts);
+
+ const goToHostAlerts = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getTabsOnHostsUrl(HostsTableType.alerts, urlSearch),
+ });
+ },
+ [navigateToApp, urlSearch]
+ );
const alertsCountViewAlertsButton = useMemo(
() => (
-
{i18n.VIEW_ALERTS}
-
+
),
- [urlSearch]
+ [goToHostAlerts, formatUrl]
);
const alertsByCategoryHistogramConfigs: MatrixHisrogramConfigs = useMemo(
diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx
index 49fe609e0cf9..95dd65f55947 100644
--- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx
@@ -13,6 +13,8 @@ import { mockIndexPattern, TestProviders } from '../../../common/mock';
import { EventCounts } from '.';
+jest.mock('../../../common/components/link_to');
+
describe('EventCounts', () => {
const from = 1579553397080;
const to = 1579639797080;
diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
index 0065e5a61b3f..fe3f9f8ecda3 100644
--- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
@@ -5,12 +5,11 @@
*/
import { Position } from '@elastic/charts';
-import { EuiButton } from '@elastic/eui';
import numeral from '@elastic/numeral';
-import React, { useEffect, useMemo } from 'react';
+import React, { useEffect, useMemo, useCallback } from 'react';
import uuid from 'uuid';
-import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
+import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants';
import { SHOWING, UNIT } from '../../../common/components/events_viewer/translations';
import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts';
import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram';
@@ -18,8 +17,6 @@ import {
MatrixHisrogramConfigs,
MatrixHistogramOption,
} from '../../../common/components/matrix_histogram/types';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
import { eventsStackByOptions } from '../../../hosts/pages/navigation';
import { convertToBuildEsQuery } from '../../../common/lib/keury';
import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
@@ -35,6 +32,9 @@ import { HostsTableType, HostsType } from '../../../hosts/store/model';
import { InputsModelId } from '../../../common/store/inputs/constants';
import * as i18n from '../../pages/translations';
+import { SecurityPageName } from '../../../app/types';
+import { useFormatUrl } from '../../../common/components/link_to';
+import { LinkButton } from '../../../common/components/links';
const NO_FILTERS: Filter[] = [];
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
@@ -95,16 +95,30 @@ const EventsByDatasetComponent: React.FC = ({
}, [deleteQuery, uniqueQueryId]);
const kibana = useKibana();
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.hosts);
+ const { navigateToApp } = kibana.services.application;
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
- const urlSearch = useGetUrlSearch(navTabs.hosts);
+
+ const goToHostEvents = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getTabsOnHostsUrl(HostsTableType.events, urlSearch),
+ });
+ },
+ [navigateToApp, urlSearch]
+ );
const eventsCountViewEventsButton = useMemo(
() => (
-
+
{i18n.VIEW_EVENTS}
-
+
),
- [urlSearch]
+ [goToHostEvents, formatUrl]
);
const filterQuery = useMemo(
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
index 8b04c329731a..d29efa2d44c1 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
@@ -25,6 +25,7 @@ import { GetOverviewHostQuery } from '../../../graphql/types';
import { wait } from '../../../common/lib/helpers';
jest.mock('../../../common/lib/kibana');
+jest.mock('../../../common/components/link_to');
const startDate = 1579553397080;
const endDate = 1579639797080;
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
index 4fb37a997924..195bb4fa0807 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
@@ -5,23 +5,23 @@
*/
import { isEmpty } from 'lodash/fp';
-import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
+import { EuiFlexItem, EuiPanel } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { FormattedMessage } from '@kbn/i18n/react';
-import React, { useMemo } from 'react';
+import React, { useMemo, useCallback } from 'react';
-import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
+import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants';
import { ESQuery } from '../../../../common/typed_json';
import { ID as OverviewHostQueryId, OverviewHostQuery } from '../../containers/overview_host';
import { HeaderSection } from '../../../common/components/header_section';
-import { useUiSetting$ } from '../../../common/lib/kibana';
-import { getHostsUrl } from '../../../common/components/link_to';
+import { useUiSetting$, useKibana } from '../../../common/lib/kibana';
+import { getHostsUrl, useFormatUrl } from '../../../common/components/link_to';
import { getOverviewHostStats, OverviewHostStats } from '../overview_host_stats';
import { manageQuery } from '../../../common/components/page/manage_query';
import { inputsModel } from '../../../common/store/inputs';
import { InspectButtonContainer } from '../../../common/components/inspect';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
+import { SecurityPageName } from '../../../app/types';
+import { LinkButton } from '../../../common/components/links';
export interface OwnProps {
startDate: number;
@@ -49,18 +49,30 @@ const OverviewHostComponent: React.FC = ({
startDate,
setQuery,
}) => {
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.hosts);
+ const { navigateToApp } = useKibana().services.application;
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
- const urlSearch = useGetUrlSearch(navTabs.hosts);
+
+ const goToHost = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getHostsUrl(urlSearch),
+ });
+ },
+ [navigateToApp, urlSearch]
+ );
+
const hostPageButton = useMemo(
() => (
-
+
-
+
),
- [urlSearch]
+ [goToHost, formatUrl]
);
return (
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
index 2fcf6f83f0ae..b4b685465dbd 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
@@ -22,6 +22,7 @@ import { overviewNetworkQuery } from '../../containers/overview_network/index.gq
import { GetOverviewHostQuery } from '../../../graphql/types';
import { wait } from '../../../common/lib/helpers';
+jest.mock('../../../common/components/link_to');
jest.mock('../../../common/lib/kibana');
const startDate = 1579553397080;
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
index 94a3c13e3994..31544eaa2d3b 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
@@ -5,15 +5,15 @@
*/
import { isEmpty } from 'lodash/fp';
-import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
+import { EuiFlexItem, EuiPanel } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { FormattedMessage } from '@kbn/i18n/react';
-import React, { useMemo } from 'react';
+import React, { useMemo, useCallback } from 'react';
-import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
+import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants';
import { ESQuery } from '../../../../common/typed_json';
import { HeaderSection } from '../../../common/components/header_section';
-import { useUiSetting$ } from '../../../common/lib/kibana';
+import { useUiSetting$, useKibana } from '../../../common/lib/kibana';
import { manageQuery } from '../../../common/components/page/manage_query';
import {
ID as OverviewNetworkQueryId,
@@ -21,10 +21,10 @@ import {
} from '../../containers/overview_network';
import { inputsModel } from '../../../common/store/inputs';
import { getOverviewNetworkStats, OverviewNetworkStats } from '../overview_network_stats';
-import { getNetworkUrl } from '../../../common/components/link_to';
+import { getNetworkUrl, useFormatUrl } from '../../../common/components/link_to';
import { InspectButtonContainer } from '../../../common/components/inspect';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
+import { SecurityPageName } from '../../../app/types';
+import { LinkButton } from '../../../common/components/links';
export interface OverviewNetworkProps {
startDate: number;
@@ -51,19 +51,32 @@ const OverviewNetworkComponent: React.FC = ({
startDate,
setQuery,
}) => {
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.hosts);
+ const { navigateToApp } = useKibana().services.application;
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
- const urlSearch = useGetUrlSearch(navTabs.network);
+
+ const goToNetwork = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getNetworkUrl(urlSearch),
+ });
+ },
+ [navigateToApp, urlSearch]
+ );
+
const networkPageButton = useMemo(
() => (
-
+
-
+
),
- [urlSearch]
+ [goToNetwork, formatUrl]
);
+
return (
diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx
index 03c1754f1b8d..2618ad950a77 100644
--- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx
@@ -4,18 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
-import React, { useEffect, useMemo, useRef } from 'react';
+import { EuiHorizontalRule, EuiText } from '@elastic/eui';
+import React, { useEffect, useMemo, useRef, useCallback } from 'react';
import { FilterOptions, QueryParams } from '../../../cases/containers/types';
import { DEFAULT_QUERY_PARAMS, useGetCases } from '../../../cases/containers/use_get_cases';
-import { getCaseUrl } from '../../../common/components/link_to/redirect_to_case';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
import { LoadingPlaceholders } from '../loading_placeholders';
import { NoCases } from './no_cases';
import { RecentCases } from './recent_cases';
import * as i18n from './translations';
+import { useKibana } from '../../../common/lib/kibana';
+import { APP_ID } from '../../../../common/constants';
+import { SecurityPageName } from '../../../app/types';
+import { useFormatUrl } from '../../../common/components/link_to';
+import { LinkAnchor } from '../../../common/components/links';
const usePrevious = (value: FilterOptions) => {
const ref = useRef();
@@ -34,16 +36,31 @@ const queryParams: QueryParams = {
const StatefulRecentCasesComponent = React.memo(
({ filterOptions }: { filterOptions: FilterOptions }) => {
+ const { formatUrl } = useFormatUrl(SecurityPageName.case);
+ const { navigateToApp } = useKibana().services.application;
const previousFilterOptions = usePrevious(filterOptions);
const { data, loading, setFilters } = useGetCases(queryParams);
const isLoadingCases = useMemo(
() => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1,
[loading]
);
- const search = useGetUrlSearch(navTabs.case);
+
+ const goToCases = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`);
+ },
+ [navigateToApp]
+ );
+
const allCasesLink = useMemo(
- () => {i18n.VIEW_ALL_CASES} ,
- [search]
+ () => (
+
+ {' '}
+ {i18n.VIEW_ALL_CASES}
+
+ ),
+ [goToCases, formatUrl]
);
useEffect(() => {
diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx
index e29223ca07e6..40969a6e1df4 100644
--- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx
@@ -4,20 +4,37 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiLink } from '@elastic/eui';
-import React, { useMemo } from 'react';
+import React, { useMemo, useCallback } from 'react';
+import { APP_ID } from '../../../../../common/constants';
import { getCreateCaseUrl } from '../../../../common/components/link_to/redirect_to_case';
-import { useGetUrlSearch } from '../../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../../app/home/home_navigations';
-
+import { LinkAnchor } from '../../../../common/components/links';
+import { useFormatUrl } from '../../../../common/components/link_to';
import * as i18n from '../translations';
+import { useKibana } from '../../../../common/lib/kibana';
+import { SecurityPageName } from '../../../../app/types';
const NoCasesComponent = () => {
- const urlSearch = useGetUrlSearch(navTabs.case);
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.case);
+ const { navigateToApp } = useKibana().services.application;
+
+ const goToCreateCase = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
+ path: getCreateCaseUrl(search),
+ });
+ },
+ [navigateToApp, search]
+ );
const newCaseLink = useMemo(
- () => {` ${i18n.START_A_NEW_CASE}`} ,
- [urlSearch]
+ () => (
+ {` ${i18n.START_A_NEW_CASE}`}
+ ),
+ [formatUrl, goToCreateCase]
);
return (
diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx
index 9618ddb05716..5867a9d859f0 100644
--- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx
@@ -4,18 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import { Case } from '../../../cases/containers/types';
import { getCaseDetailsUrl } from '../../../common/components/link_to/redirect_to_case';
import { Markdown } from '../../../common/components/markdown';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
+import { useFormatUrl } from '../../../common/components/link_to';
import { IconWithCount } from '../recent_timelines/counts';
-
+import { LinkAnchor } from '../../../common/components/links';
import * as i18n from './translations';
+import { useKibana } from '../../../common/lib/kibana';
+import { APP_ID } from '../../../../common/constants';
+import { SecurityPageName } from '../../../app/types';
const MarkdownContainer = styled.div`
max-height: 150px;
@@ -24,7 +26,8 @@ const MarkdownContainer = styled.div`
`;
const RecentCasesComponent = ({ cases }: { cases: Case[] }) => {
- const search = useGetUrlSearch(navTabs.case);
+ const { formatUrl, search } = useFormatUrl(SecurityPageName.case);
+ const { navigateToApp } = useKibana().services.application;
return (
<>
@@ -32,7 +35,17 @@ const RecentCasesComponent = ({ cases }: { cases: Case[] }) => {
- {c.title}
+ void }) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCaseDetailsUrl({ id: c.id, search }),
+ });
+ }}
+ href={formatUrl(getCaseDetailsUrl({ id: c.id }))}
+ >
+ {c.title}
+
diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx
index ab76219b3cc0..9c149a850bec 100644
--- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx
@@ -5,7 +5,7 @@
*/
import ApolloClient from 'apollo-client';
-import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
+import { EuiHorizontalRule, EuiText } from '@elastic/eui';
import React, { useCallback, useMemo, useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { Dispatch } from 'redux';
@@ -23,10 +23,12 @@ import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/s
import { RecentTimelines } from './recent_timelines';
import * as i18n from './translations';
import { FilterMode } from './types';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
-import { navTabs } from '../../../app/home/home_navigations';
-import { getTimelinesUrl } from '../../../common/components/link_to/redirect_to_timelines';
import { LoadingPlaceholders } from '../loading_placeholders';
+import { useKibana } from '../../../common/lib/kibana';
+import { SecurityPageName } from '../../../app/types';
+import { APP_ID } from '../../../../common/constants';
+import { useFormatUrl } from '../../../common/components/link_to';
+import { LinkAnchor } from '../../../common/components/links';
interface OwnProps {
apolloClient: ApolloClient<{}>;
@@ -39,6 +41,8 @@ const PAGE_SIZE = 3;
const StatefulRecentTimelinesComponent = React.memo(
({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => {
+ const { formatUrl } = useFormatUrl(SecurityPageName.timelines);
+ const { navigateToApp } = useKibana().services.application;
const onOpenTimeline: OnOpenTimeline = useCallback(
({ duplicate, timelineId }: { duplicate: boolean; timelineId: string }) => {
queryTimelineById({
@@ -52,12 +56,24 @@ const StatefulRecentTimelinesComponent = React.memo(
[apolloClient, updateIsLoading, updateTimeline]
);
+ const goToTimelines = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`);
+ },
+ [navigateToApp]
+ );
+
const noTimelinesMessage =
filterBy === 'favorites' ? i18n.NO_FAVORITE_TIMELINES : i18n.NO_TIMELINES;
- const urlSearch = useGetUrlSearch(navTabs.timelines);
+
const linkAllTimelines = useMemo(
- () => {i18n.VIEW_ALL_TIMELINES} ,
- [urlSearch]
+ () => (
+
+ {i18n.VIEW_ALL_TIMELINES}
+
+ ),
+ [goToTimelines, formatUrl]
);
const loadingPlaceholders = useMemo(
() => (
@@ -68,22 +84,24 @@ const StatefulRecentTimelinesComponent = React.memo(
const { fetchAllTimeline, timelines, loading } = useGetAllTimeline();
- useEffect(() => {
- fetchAllTimeline({
- pageInfo: {
- pageIndex: 1,
- pageSize: PAGE_SIZE,
- },
- search: '',
- sort: {
- sortField: SortFieldTimeline.updated,
- sortOrder: Direction.desc,
- },
- onlyUserFavorite: filterBy === 'favorites',
- timelineType: TimelineType.default,
- });
+ useEffect(
+ () =>
+ fetchAllTimeline({
+ pageInfo: {
+ pageIndex: 1,
+ pageSize: PAGE_SIZE,
+ },
+ search: '',
+ sort: {
+ sortField: SortFieldTimeline.updated,
+ sortOrder: Direction.desc,
+ },
+ onlyUserFavorite: filterBy === 'favorites',
+ timelineType: TimelineType.default,
+ }),
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [filterBy]);
+ [filterBy]
+ );
return (
<>
diff --git a/x-pack/plugins/security_solution/public/overview/index.ts b/x-pack/plugins/security_solution/public/overview/index.ts
index bdf855b3851c..dad9d0c1dc1f 100644
--- a/x-pack/plugins/security_solution/public/overview/index.ts
+++ b/x-pack/plugins/security_solution/public/overview/index.ts
@@ -5,14 +5,14 @@
*/
import { SecuritySubPlugin } from '../app/types';
-import { getOverviewRoutes } from './routes';
+import { OverviewRoutes } from './routes';
export class Overview {
public setup() {}
public start(): SecuritySubPlugin {
return {
- routes: getOverviewRoutes(),
+ SubPluginRoutes: OverviewRoutes,
};
}
}
diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
index 57a82f6f254f..543dafd50c8e 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
@@ -28,6 +28,7 @@ import { SignalsByCategory } from '../components/signals_by_category';
import { inputsSelectors, State } from '../../common/store';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../common/store/inputs/actions';
import { SpyRoute } from '../../common/utils/route/spy_routes';
+import { SecurityPageName } from '../../app/types';
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
const NO_FILTERS: Filter[] = [];
@@ -120,7 +121,7 @@ const OverviewComponent: React.FC = ({
}
-
+
>
);
diff --git a/x-pack/plugins/security_solution/public/overview/routes.tsx b/x-pack/plugins/security_solution/public/overview/routes.tsx
index fc41227b27c0..ff26475d7b25 100644
--- a/x-pack/plugins/security_solution/public/overview/routes.tsx
+++ b/x-pack/plugins/security_solution/public/overview/routes.tsx
@@ -5,11 +5,14 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { Overview } from './pages';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getOverviewRoutes = () => [
- } />,
-];
+export const OverviewRoutes = () => (
+
+ } />
+ } />
+
+);
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index b9eef9f799d3..58f0a0ddb749 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -5,6 +5,9 @@
*/
import { i18n } from '@kbn/i18n';
+import { Store, Action } from 'redux';
+import { BehaviorSubject } from 'rxjs';
+import { pluck } from 'rxjs/operators';
import {
AppMountParameters,
@@ -20,11 +23,26 @@ import { initTelemetry } from './common/lib/telemetry';
import { KibanaServices } from './common/lib/kibana/services';
import { serviceNowActionType, jiraActionType } from './common/lib/connectors';
import { PluginSetup, PluginStart, SetupPlugins, StartPlugins, StartServices } from './types';
-import { APP_ID, APP_NAME, APP_ICON, APP_PATH } from '../common/constants';
+import {
+ APP_ID,
+ APP_ICON,
+ APP_ALERTS_PATH,
+ APP_HOSTS_PATH,
+ APP_OVERVIEW_PATH,
+ APP_NETWORK_PATH,
+ APP_TIMELINES_PATH,
+ APP_MANAGEMENT_PATH,
+ APP_CASES_PATH,
+} from '../common/constants';
import { ConfigureEndpointDatasource } from './management/pages/policy/view/ingest_manager_integration/configure_datasource';
+import { State, createStore, createInitialState } from './common/store';
+import { SecurityPageName } from './app/types';
+import { manageOldSiemRoutes } from './helpers';
+
export class Plugin implements IPlugin {
private kibanaVersion: string;
+ private store!: Store;
constructor(initializerContext: PluginInitializerContext) {
this.kibanaVersion = initializerContext.env.packageInfo.version;
@@ -42,7 +60,7 @@ export class Plugin implements IPlugin {
+ const mountSecurityFactory = async () => {
const storage = new Storage(localStorage);
const [coreStart, startPlugins] = await core.getStartServices();
- const { renderApp } = await import('./app');
+ if (this.store == null) {
+ await this.buildStore(coreStart, startPlugins, storage);
+ }
+
const services = {
...coreStart,
...startPlugins,
- security: plugins.security,
storage,
+ security: plugins.security,
} as StartServices;
-
- const alertsSubPlugin = new (await import('./alerts')).Alerts();
- const casesSubPlugin = new (await import('./cases')).Cases();
- const hostsSubPlugin = new (await import('./hosts')).Hosts();
- const networkSubPlugin = new (await import('./network')).Network();
- const overviewSubPlugin = new (await import('./overview')).Overview();
- const timelinesSubPlugin = new (await import('./timelines')).Timelines();
- const endpointAlertsSubPlugin = new (await import('./endpoint_alerts')).EndpointAlerts();
- const managementSubPlugin = new (await import('./management')).Management();
-
- const alertsStart = alertsSubPlugin.start(storage);
- const casesStart = casesSubPlugin.start();
- const hostsStart = hostsSubPlugin.start(storage);
- const networkStart = networkSubPlugin.start(storage);
- const overviewStart = overviewSubPlugin.start();
- const timelinesStart = timelinesSubPlugin.start();
- const endpointAlertsStart = endpointAlertsSubPlugin.start(coreStart, startPlugins);
- const managementSubPluginStart = managementSubPlugin.start(coreStart, startPlugins);
-
- const timelineInitialState = {
- timeline: {
- ...timelinesStart.store.initialState.timeline!,
- timelineById: {
- ...timelinesStart.store.initialState.timeline!.timelineById,
- ...alertsStart.storageTimelines!.timelineById,
- ...hostsStart.storageTimelines!.timelineById,
- ...networkStart.storageTimelines!.timelineById,
- },
- },
- };
-
- return renderApp(services, params, {
- routes: [
- ...alertsStart.routes,
- ...casesStart.routes,
- ...hostsStart.routes,
- ...networkStart.routes,
- ...overviewStart.routes,
- ...timelinesStart.routes,
- ...endpointAlertsStart.routes,
- ...managementSubPluginStart.routes,
- ],
- store: {
- initialState: {
- ...hostsStart.store.initialState,
- ...networkStart.store.initialState,
- ...timelineInitialState,
- ...endpointAlertsStart.store.initialState,
- ...managementSubPluginStart.store.initialState,
- },
- reducer: {
- ...hostsStart.store.reducer,
- ...networkStart.store.reducer,
- ...timelinesStart.store.reducer,
- ...endpointAlertsStart.store.reducer,
- ...managementSubPluginStart.store.reducer,
- },
- middlewares: [
- ...(endpointAlertsStart.store.middleware ?? []),
- ...(managementSubPluginStart.store.middleware ?? []),
- ],
- },
- });
+ return { coreStart, startPlugins, services, store: this.store, storage };
};
+ // Waiting for https://github.com/elastic/kibana/issues/69110
+ // core.application.register({
+ // id: APP_ID,
+ // title: 'Security',
+ // appRoute: APP_PATH,
+ // navLinkStatus: AppNavLinkStatus.hidden,
+ // mount: async (params: AppMountParameters) => {
+ // const [{ application }] = await core.getStartServices();
+ // application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { replace: true });
+ // return () => true;
+ // },
+ // });
+
core.application.register({
- id: APP_ID,
- title: APP_NAME,
+ id: `${APP_ID}:${SecurityPageName.overview}`,
+ title: i18n.translate('xpack.securitySolution.overviewPage.title', {
+ defaultMessage: 'Overview',
+ }),
order: 9000,
euiIconType: APP_ICON,
category: DEFAULT_APP_CATEGORIES.security,
- appRoute: APP_PATH,
- async mount(params: AppMountParameters) {
- return mountSecurityApp(params);
+ appRoute: APP_OVERVIEW_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services },
+ { renderApp, composeLibs },
+ { overviewSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: overviewSubPlugin.start().SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.alerts}`,
+ title: i18n.translate('xpack.securitySolution.alertsPage.title', {
+ defaultMessage: 'Alerts',
+ }),
+ order: 9001,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_ALERTS_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services, storage },
+ { renderApp, composeLibs },
+ { alertsSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: alertsSubPlugin.start(storage).SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.hosts}`,
+ title: 'Hosts',
+ order: 9002,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_HOSTS_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services, storage },
+ { renderApp, composeLibs },
+ { hostsSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: hostsSubPlugin.start(storage).SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.network}`,
+ title: 'Network',
+ order: 9002,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_NETWORK_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services, storage },
+ { renderApp, composeLibs },
+ { networkSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: networkSubPlugin.start(storage).SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.timelines}`,
+ title: 'Timelines',
+ order: 9002,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_TIMELINES_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services },
+ { renderApp, composeLibs },
+ { timelinesSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: timelinesSubPlugin.start().SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.case}`,
+ title: 'Cases',
+ order: 9002,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_CASES_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, store, services },
+ { renderApp, composeLibs },
+ { casesSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: casesSubPlugin.start().SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: `${APP_ID}:${SecurityPageName.management}`,
+ title: 'Management',
+ order: 9002,
+ euiIconType: APP_ICON,
+ category: DEFAULT_APP_CATEGORIES.security,
+ appRoute: APP_MANAGEMENT_PATH,
+ mount: async (params: AppMountParameters) => {
+ const [
+ { coreStart, startPlugins, store, services },
+ { renderApp, composeLibs },
+ { managementSubPlugin },
+ ] = await Promise.all([
+ mountSecurityFactory(),
+ this.downloadAssets(),
+ this.downloadSubPlugins(),
+ ]);
+ return renderApp({
+ ...composeLibs(coreStart),
+ ...params,
+ services,
+ store,
+ SubPluginRoutes: managementSubPlugin.start(coreStart, startPlugins).SubPluginRoutes,
+ });
+ },
+ });
+
+ core.application.register({
+ id: 'siem',
+ appRoute: 'app/siem',
+ title: 'SIEM',
+ navLinkStatus: 3,
+ mount: async (params: AppMountParameters) => {
+ const [coreStart] = await core.getStartServices();
+ manageOldSiemRoutes(coreStart);
+ return () => true;
},
});
@@ -150,4 +315,97 @@ export class Plugin implements IPlugin(
- ({ dataProviders, flyoutHeight, show, showTimeline, timelineId, usersViewing, width }) => {
+ ({ dataProviders, flyoutHeight, show = true, showTimeline, timelineId, usersViewing, width }) => {
const handleClose = useCallback(() => showTimeline({ id: timelineId, show: false }), [
showTimeline,
timelineId,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx
index 2615d5057347..24f8d910b4fe 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx
@@ -120,6 +120,8 @@ const getNetflowInstance = () => (
/>
);
+jest.mock('../../../common/components/link_to');
+
describe('Netflow', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx
index 344f10dfdb35..24dee1460810 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx
@@ -109,19 +109,25 @@ export const StatefulOpenTimelineComponent = React.memo(
const { timelineType, timelineTabs, timelineFilters } = useTimelineTypes();
const { fetchAllTimeline, timelines, loading, totalCount } = useGetAllTimeline();
- const refetch = useCallback(() => {
- fetchAllTimeline({
- pageInfo: {
- pageIndex: pageIndex + 1,
- pageSize,
- },
- search,
- sort: { sortField: sortField as SortFieldTimeline, sortOrder: sortDirection as Direction },
- onlyUserFavorite: onlyFavorites,
- timelineType,
- });
+ const refetch = useCallback(
+ () =>
+ fetchAllTimeline({
+ pageInfo: {
+ pageIndex: pageIndex + 1,
+ pageSize,
+ },
+ search,
+ sort: {
+ sortField: sortField as SortFieldTimeline,
+ sortOrder: sortDirection as Direction,
+ },
+ onlyUserFavorite: onlyFavorites,
+ timelineType,
+ }),
+
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [pageIndex, pageSize, search, sortField, sortDirection, timelineType, onlyFavorites]);
+ [pageIndex, pageSize, search, sortField, sortDirection, timelineType, onlyFavorites]
+ );
/** Invoked when the user presses enters to submit the text in the search input */
const onQueryChange: OnQueryChange = useCallback((query: EuiSearchBarQuery) => {
@@ -251,9 +257,7 @@ export const StatefulOpenTimelineComponent = React.memo(
focusInput();
}, []);
- useEffect(() => {
- refetch();
- }, [refetch]);
+ useEffect(() => refetch(), [refetch]);
return !isModal ? (
void }) => void;
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx
index afc66e337d7b..56c67b0c294a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx
@@ -4,14 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useState, useCallback, useMemo } from 'react';
-import { useParams } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { EuiTabs, EuiTab, EuiSpacer, EuiFilterButton } from '@elastic/eui';
import { TimelineTypeLiteralWithNull, TimelineType } from '../../../../common/types/timeline';
-
-import { getTimelineTabsUrl } from '../../../common/components/link_to';
-import { navTabs } from '../../../app/home/home_navigations';
-import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
+import { SecurityPageName } from '../../../app/types';
+import { getTimelineTabsUrl, useFormatUrl } from '../../../common/components/link_to';
import * as i18n from './translations';
import { TimelineTabsStyle, TimelineTab } from './types';
@@ -20,12 +18,29 @@ export const useTimelineTypes = (): {
timelineTabs: JSX.Element;
timelineFilters: JSX.Element;
} => {
- const urlSearch = useGetUrlSearch(navTabs.timelines);
+ const history = useHistory();
+ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.timelines);
const { tabName } = useParams<{ pageName: string; tabName: string }>();
const [timelineType, setTimelineTypes] = useState(
tabName === TimelineType.default || tabName === TimelineType.template ? tabName : null
);
+ const goToTimeline = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getTimelineTabsUrl(TimelineType.default, urlSearch));
+ },
+ [history, urlSearch]
+ );
+
+ const goToTemplateTimeline = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ history.push(getTimelineTabsUrl(TimelineType.template, urlSearch));
+ },
+ [history, urlSearch]
+ );
+
const getFilterOrTabs: (timelineTabsStyle: TimelineTabsStyle) => TimelineTab[] = (
timelineTabsStyle: TimelineTabsStyle
) => [
@@ -35,8 +50,9 @@ export const useTimelineTypes = (): {
timelineTabsStyle === TimelineTabsStyle.filter
? i18n.FILTER_TIMELINES(i18n.TAB_TIMELINES)
: i18n.TAB_TIMELINES,
- href: getTimelineTabsUrl(TimelineType.default, urlSearch),
+ href: formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)),
disabled: false,
+ onClick: goToTimeline,
},
{
id: TimelineType.template,
@@ -44,8 +60,9 @@ export const useTimelineTypes = (): {
timelineTabsStyle === TimelineTabsStyle.filter
? i18n.FILTER_TIMELINES(i18n.TAB_TEMPLATES)
: i18n.TAB_TEMPLATES,
- href: getTimelineTabsUrl(TimelineType.template, urlSearch),
+ href: formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)),
disabled: false,
+ onClick: goToTemplateTimeline,
},
];
@@ -70,7 +87,10 @@ export const useTimelineTypes = (): {
disabled={tab.disabled}
key={`timeline-${TimelineTabsStyle.tab}-${tab.id}`}
href={tab.href}
- onClick={onFilterClicked.bind(null, TimelineTabsStyle.tab, tab.id)}
+ onClick={(ev) => {
+ tab.onClick(ev);
+ onFilterClicked(TimelineTabsStyle.tab, tab.id);
+ }}
>
{tab.name}
@@ -89,7 +109,10 @@ export const useTimelineTypes = (): {
void }) => {
+ tab.onClick(ev);
+ onFilterClicked.bind(null, TimelineTabsStyle.filter, tab.id);
+ }}
>
{tab.name}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
index 67a13b2f1ff1..9ced194aceb3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
@@ -24,6 +24,8 @@ const mockSort: Sort = {
sortDirection: Direction.desc,
};
+jest.mock('../../../../common/components/link_to');
+
jest.mock(
'react-visibility-sensor',
() => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) =>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
index ae5e7e2ef789..aec463f53144 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
@@ -19,6 +19,7 @@ import {
createGenericFileRowRenderer,
} from './generic_row_renderer';
+jest.mock('../../../../../../common/components/link_to');
jest.mock('../../../../../../overview/components/events_by_dataset');
describe('GenericRowRenderer', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx
index 64f4656e7e79..3e055682d27a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx
@@ -16,6 +16,7 @@ import { FormattedFieldValue } from './formatted_field';
import { HOST_NAME_FIELD_NAME } from './constants';
jest.mock('../../../../../common/lib/kibana');
+jest.mock('../../../../../common/components/link_to');
describe('Events', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
index aaac3e7f7f4d..b2588e19800a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
@@ -30,7 +30,7 @@ import {
RULE_REFERENCE_FIELD_NAME,
SIGNAL_RULE_NAME_FIELD_NAME,
} from './constants';
-import { renderRuleName, renderEventModule, renderRulReference } from './formatted_field_helpers';
+import { RenderRuleName, renderEventModule, renderRulReference } from './formatted_field_helpers';
// simple black-list to prevent dragging and dropping fields such as message name
const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
@@ -95,7 +95,16 @@ const FormattedFieldValueComponent: React.FC<{
);
} else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) {
- return renderRuleName({ contextId, eventId, fieldName, linkValue, truncate, value });
+ return (
+
+ );
} else if (fieldName === EVENT_MODULE_FIELD_NAME) {
return renderEventModule({ contextId, eventId, fieldName, linkValue, truncate, value });
} else if (fieldName === RULE_REFERENCE_FIELD_NAME) {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx
index b24c262c8a2e..81820e2253fc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx
@@ -6,7 +6,7 @@
import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui';
import { isString, isEmpty } from 'lodash/fp';
-import React from 'react';
+import React, { useCallback } from 'react';
import styled from 'styled-components';
import { DefaultDraggable } from '../../../../../common/components/draggables';
@@ -18,31 +18,49 @@ import { isUrlInvalid } from '../../../../../common/utils/validators';
import endPointSvg from '../../../../../common/utils/logo_endpoint/64_color.svg';
import * as i18n from './translations';
+import { SecurityPageName } from '../../../../../app/types';
+import { useFormatUrl } from '../../../../../common/components/link_to';
+import { useKibana } from '../../../../../common/lib/kibana';
+import { APP_ID } from '../../../../../../common/constants';
+import { LinkAnchor } from '../../../../../common/components/links';
const EventModuleFlexItem = styled(EuiFlexItem)`
width: 100%;
`;
-export const renderRuleName = ({
- contextId,
- eventId,
- fieldName,
- linkValue,
- truncate,
- value,
-}: {
+interface RenderRuleNameProps {
contextId: string;
eventId: string;
fieldName: string;
linkValue: string | null | undefined;
truncate?: boolean;
value: string | number | null | undefined;
+}
+
+export const RenderRuleName: React.FC = ({
+ contextId,
+ eventId,
+ fieldName,
+ linkValue,
+ truncate,
+ value,
}) => {
const ruleName = `${value}`;
const ruleId = linkValue;
-
+ const { search } = useFormatUrl(SecurityPageName.alerts);
+ const { navigateToApp, getUrlForApp } = useKibana().services.application;
const content = truncate ? {value} : value;
+ const goToRuleDetails = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ navigateToApp(`${APP_ID}:${SecurityPageName.alerts}`, {
+ path: getRuleDetailsUrl(ruleId ?? '', search),
+ });
+ },
+ [navigateToApp, ruleId, search]
+ );
+
return isString(value) && ruleName.length > 0 && ruleId != null ? (
- {content}
+
+ {content}
+
) : (
getEmptyTagValue()
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx
index 3222f8a2362d..0b3ea0ce6e43 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx
@@ -17,6 +17,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { rowRenderers } from '.';
import { getRowRenderer } from './get_row_renderer';
+jest.mock('../../../../../common/components/link_to');
+
describe('get_column_renderer', () => {
let nonSuricata: Ecs;
let suricata: Ecs;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx
index 7e51c717ebe8..5140b9abc60e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx
@@ -24,6 +24,8 @@ export const justIdAndTimestamp: Ecs = {
timestamp: '2018-11-12T19:03:25.936Z',
};
+jest.mock('../../../../../../common/components/link_to');
+
describe('netflowRowRenderer', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx
index 2448540f27e2..b7c2cb7032cc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx
@@ -16,6 +16,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { plainColumnRenderer } from './plain_column_renderer';
import { getValues, deleteItemIdx, findItem } from './helpers';
+jest.mock('../../../../../common/components/link_to');
+
describe('plain_column_renderer', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx
index d5040cb25237..14f147c61fca 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../../common/mock/test_providers';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { SuricataDetails } from './suricata_details';
+jest.mock('../../../../../../common/components/link_to');
+
describe('SuricataDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx
index a10cd9dc97f6..d36d24f41224 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx
@@ -15,6 +15,8 @@ import { TestProviders } from '../../../../../../common/mock/test_providers';
import { suricataRowRenderer } from './suricata_row_renderer';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/components/link_to');
+
describe('suricata_row_renderer', () => {
const mount = useMountAppended();
let nonSuricata: Ecs;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx
index e622c91e8b87..8efd8e194433 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx
@@ -13,6 +13,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { SystemGenericDetails, SystemGenericLine } from './generic_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/components/link_to');
+
describe('SystemGenericDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
index 61cc7f32567e..5f4f5a5da352 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
@@ -13,6 +13,14 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { SystemGenericFileDetails, SystemGenericFileLine } from './generic_file_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory: () => ({
+ createHref: jest.fn(),
+ push: jest.fn(),
+ }),
+}));
+
describe('SystemGenericFileDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
index 26cccc82896e..bb997b968927 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
@@ -48,6 +48,7 @@ import {
} from './generic_row_renderer';
import * as i18n from './translations';
+jest.mock('../../../../../../common/components/link_to');
jest.mock('../../../../../../overview/components/events_by_dataset');
describe('GenericRowRenderer', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx
index d82a0fcb6e64..04b0e6e5fcfa 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx
@@ -11,6 +11,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { ZeekDetails } from './zeek_details';
+jest.mock('../../../../../../common/components/link_to');
+
describe('ZeekDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx
index 2197ccb0ce2e..2eed6aaf2033 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx
@@ -14,6 +14,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { zeekRowRenderer } from './zeek_row_renderer';
+jest.mock('../../../../../../common/components/link_to');
+
describe('zeek_row_renderer', () => {
const mount = useMountAppended();
let nonZeek: Ecs;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
index 311012986762..b11f6dfdf9d8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
@@ -70,6 +70,7 @@ describe('StatefulTimeline', () => {
filters: [],
id: 'foo',
isLive: false,
+ isTimelineExists: false,
itemsPerPage: 5,
itemsPerPageOptions: [5, 10, 20],
kqlMode: 'search',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
index b53a34041a68..51cfe8ae33b0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
@@ -43,6 +43,7 @@ const StatefulTimelineComponent = React.memo(
filters,
id,
isLive,
+ isTimelineExists,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
@@ -151,7 +152,7 @@ const StatefulTimelineComponent = React.memo(
);
useEffect(() => {
- if (createTimeline != null) {
+ if (createTimeline != null && !isTimelineExists) {
createTimeline({ id, columns: defaultHeaders, show: false });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -248,6 +249,7 @@ const makeMapStateToProps = () => {
filters: timelineFilter,
id,
isLive: input.policy.kind === 'interval',
+ isTimelineExists: getTimeline(state, id) != null,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
index 6269bc1b4a1a..c3def9c4cbb2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
@@ -17,7 +17,7 @@ export const useInsertTimeline = (form: FormHook, fieldNa
});
const handleOnTimelineChange = useCallback(
(title: string, id: string | null) => {
- const builtLink = `${basePath}/app/security#/timelines?timeline=(id:'${id}',isOpen:!t)`;
+ const builtLink = `${basePath}/app/security/timelines?timeline=(id:'${id}',isOpen:!t)`;
const currentValue = form.getFormData()[fieldName];
const newValue: string = [
currentValue.slice(0, cursorPosition.start),
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx
index 4d5c16dc30b8..f2e7d26c9e85 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx
@@ -23,14 +23,17 @@ import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
+import { APP_ID } from '../../../../../common/constants';
import {
TimelineTypeLiteral,
TimelineStatus,
TimelineType,
} from '../../../../../common/types/timeline';
-
-import { SiemPageName } from '../../../../app/types';
+import { navTabs } from '../../../../app/home/home_navigations';
+import { SecurityPageName } from '../../../../app/types';
import { timelineSelectors } from '../../../../timelines/store/timeline';
+import { useGetUrlSearch } from '../../../../common/components/navigation/use_get_url_search';
+import { getCreateCaseUrl } from '../../../../common/components/link_to';
import { State } from '../../../../common/store';
import { useKibana } from '../../../../common/lib/kibana';
import { Note } from '../../../../common/lib/note';
@@ -145,16 +148,15 @@ interface NewCaseProps {
export const NewCase = React.memo(
({ onClosePopover, timelineId, timelineStatus, timelineTitle }) => {
const history = useHistory();
+ const urlSearch = useGetUrlSearch(navTabs.case);
const dispatch = useDispatch();
const { savedObjectId } = useSelector((state: State) =>
timelineSelectors.selectTimeline(state, timelineId)
);
+ const { navigateToApp } = useKibana().services.application;
const handleClick = useCallback(() => {
onClosePopover();
- history.push({
- pathname: `/${SiemPageName.case}/create`,
- });
dispatch(
setInsertTimeline({
timelineId,
@@ -162,8 +164,15 @@ export const NewCase = React.memo(
timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
})
);
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCreateCaseUrl(urlSearch),
+ });
+ history.push({
+ pathname: `/${SecurityPageName.case}/create`,
+ });
+
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [dispatch, onClosePopover, history, timelineId, timelineTitle]);
+ }, [dispatch, navigateToApp, onClosePopover, history, timelineId, timelineTitle, urlSearch]);
return (
{
const originalModule = jest.requireActual('../../../../common/lib/kibana');
return {
@@ -35,6 +37,7 @@ jest.mock('../../../../common/lib/kibana', () => {
crud: true,
},
},
+ navigateToApp: jest.fn(),
},
},
}),
@@ -340,7 +343,7 @@ describe('Properties', () => {
wrapper.find('[data-test-subj="settings-gear"]').at(0).simulate('click');
wrapper.find('[data-test-subj="attach-timeline-case"]').first().simulate('click');
- expect(mockHistoryPush).toBeCalledWith({ pathname: `/${SiemPageName.case}/create` });
+ expect(mockHistoryPush).toBeCalledWith({ pathname: `/${SecurityPageName.case}/create` });
expect(mockDispatch).toBeCalledWith(
setInsertTimeline({
timelineId: defaultProps.timelineId,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx
index be79c0773bf8..602a7c8191c7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx
@@ -7,7 +7,6 @@
import React, { useState, useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { useHistory } from 'react-router-dom';
import { TimelineStatus, TimelineTypeLiteral } from '../../../../../common/types/timeline';
import { useThrottledResizeObserver } from '../../../../common/components/utils';
import { Note } from '../../../../common/lib/note';
@@ -19,11 +18,14 @@ import { TimelineProperties } from './styles';
import { PropertiesRight } from './properties_right';
import { PropertiesLeft } from './properties_left';
import { AllCasesModal } from '../../../../cases/components/all_cases_modal';
-import { SiemPageName } from '../../../../app/types';
+import { SecurityPageName } from '../../../../app/types';
import * as i18n from './translations';
import { State } from '../../../../common/store';
import { timelineSelectors } from '../../../store/timeline';
import { setInsertTimeline } from '../../../store/timeline/actions';
+import { useKibana } from '../../../../common/lib/kibana';
+import { APP_ID } from '../../../../../common/constants';
+import { getCaseDetailsUrl } from '../../../../common/components/link_to';
type CreateTimeline = ({
id,
@@ -91,6 +93,7 @@ export const Properties = React.memo(
updateTitle,
usersViewing,
}) => {
+ const { navigateToApp } = useKibana().services.application;
const { ref, width = 0 } = useThrottledResizeObserver(300);
const [showActions, setShowActions] = useState(false);
const [showNotes, setShowNotes] = useState(false);
@@ -110,7 +113,6 @@ export const Properties = React.memo(
const [showCaseModal, setShowCaseModal] = useState(false);
const onCloseCaseModal = useCallback(() => setShowCaseModal(false), []);
const onOpenCaseModal = useCallback(() => setShowCaseModal(true), []);
- const history = useHistory();
const currentTimeline = useSelector((state: State) =>
timelineSelectors.selectTimeline(state, timelineId)
);
@@ -118,8 +120,8 @@ export const Properties = React.memo(
const onRowClick = useCallback(
(id: string) => {
onCloseCaseModal();
- history.push({
- pathname: `/${SiemPageName.case}/${id}`,
+ navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
+ path: getCaseDetailsUrl({ id }),
});
dispatch(
setInsertTimeline({
@@ -129,7 +131,7 @@ export const Properties = React.memo(
})
);
},
- [onCloseCaseModal, currentTimeline, dispatch, history, timelineId, title]
+ [navigateToApp, onCloseCaseModal, currentTimeline, dispatch, timelineId, title]
);
const datePickerWidth = useMemo(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx
index 2e12ebad2f99..76c3a647a943 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx
@@ -244,22 +244,24 @@ const SelectableTimelineComponent: React.FC = ({
},
};
- useEffect(() => {
- fetchAllTimeline({
- pageInfo: {
- pageIndex: 1,
- pageSize,
- },
- search: searchTimelineValue,
- sort: {
- sortField: SortFieldTimeline.updated,
- sortOrder: Direction.desc,
- },
- onlyUserFavorite: onlyFavorites,
- timelineType,
- });
+ useEffect(
+ () =>
+ fetchAllTimeline({
+ pageInfo: {
+ pageIndex: 1,
+ pageSize,
+ },
+ search: searchTimelineValue,
+ sort: {
+ sortField: SortFieldTimeline.updated,
+ sortOrder: Direction.desc,
+ },
+ onlyUserFavorite: onlyFavorites,
+ timelineType,
+ }),
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [onlyFavorites, pageSize, searchTimelineValue, timelineType]);
+ [onlyFavorites, pageSize, searchTimelineValue, timelineType]
+ );
return (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx
index 2447ebf47463..96703941f616 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx
@@ -32,19 +32,15 @@ jest.mock('use-resize-observer/polyfilled');
mockUseResizeObserver.mockImplementation(() => ({}));
-jest.mock('react-router-dom', () => {
- const originalModule = jest.requireActual('react-router-dom');
- return {
- ...originalModule,
- useHistory: jest.fn(),
- };
-});
jest.mock('../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../common/lib/kibana');
return {
...originalModule,
useKibana: jest.fn().mockReturnValue({
services: {
+ application: {
+ navigateToApp: jest.fn(),
+ },
uiSettings: {
get: jest.fn(),
},
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
index 19112221cbfd..f025cf15181c 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getOr, noop } from 'lodash/fp';
+import { getOr } from 'lodash/fp';
import memoizeOne from 'memoize-one';
import { useCallback, useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
@@ -86,15 +86,14 @@ export const useGetAllTimeline = (): AllTimelinesArgs => {
const dispatch = useDispatch();
const apolloClient = useApolloClient();
const [, dispatchToaster] = useStateToaster();
- const [allTimelines, setAllTimelines] = useState({
- fetchAllTimeline: noop,
+ const [allTimelines, setAllTimelines] = useState>({
loading: false,
totalCount: 0,
timelines: [],
});
const fetchAllTimeline = useCallback(
- async ({ onlyUserFavorite, pageInfo, search, sort, timelineType }: AllTimelinesVariables) => {
+ ({ onlyUserFavorite, pageInfo, search, sort, timelineType }: AllTimelinesVariables) => {
let didCancel = false;
const abortCtrl = new AbortController();
@@ -139,7 +138,6 @@ export const useGetAllTimeline = (): AllTimelinesArgs => {
})
);
setAllTimelines({
- fetchAllTimeline,
loading: false,
totalCount,
timelines: getAllTimeline(JSON.stringify(variables), timelines as TimelineResult[]),
@@ -154,7 +152,6 @@ export const useGetAllTimeline = (): AllTimelinesArgs => {
dispatchToaster,
});
setAllTimelines({
- fetchAllTimeline,
loading: false,
totalCount: 0,
timelines: [],
@@ -168,8 +165,7 @@ export const useGetAllTimeline = (): AllTimelinesArgs => {
abortCtrl.abort();
};
},
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [apolloClient, allTimelines]
+ [apolloClient, allTimelines, dispatch, dispatchToaster]
);
useEffect(() => {
diff --git a/x-pack/plugins/security_solution/public/timelines/index.ts b/x-pack/plugins/security_solution/public/timelines/index.ts
index 5cce258b10d1..dde98e2b9edf 100644
--- a/x-pack/plugins/security_solution/public/timelines/index.ts
+++ b/x-pack/plugins/security_solution/public/timelines/index.ts
@@ -5,7 +5,7 @@
*/
import { SecuritySubPluginWithStore } from '../app/types';
-import { getTimelinesRoutes } from './routes';
+import { TimelinesRoutes } from './routes';
import { initialTimelineState, timelineReducer } from './store/timeline/reducer';
import { TimelineState } from './store/timeline/types';
@@ -14,7 +14,7 @@ export class Timelines {
public start(): SecuritySubPluginWithStore<'timeline', TimelineState> {
return {
- routes: getTimelinesRoutes(),
+ SubPluginRoutes: TimelinesRoutes,
store: {
initialState: { timeline: initialTimelineState },
reducer: { timeline: timelineReducer },
diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx
index 73494fb585b3..91f1980309cd 100644
--- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx
@@ -4,24 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isEmpty } from 'lodash/fp';
import React from 'react';
-import { ApolloConsumer } from 'react-apollo';
-import { Switch, Route, Redirect } from 'react-router-dom';
+import { Switch, Route, useHistory } from 'react-router-dom';
import { ChromeBreadcrumb } from '../../../../../../src/core/public';
import { TimelineType } from '../../../common/types/timeline';
import { TAB_TIMELINES, TAB_TEMPLATES } from '../components/open_timeline/translations';
-import { getTimelinesUrl } from '../../common/components/link_to';
import { TimelineRouteSpyState } from '../../common/utils/route/types';
-import { SiemPageName } from '../../app/types';
-
import { TimelinesPage } from './timelines_page';
import { PAGE_TITLE } from './translations';
import { appendSearch } from '../../common/components/link_to/helpers';
-const timelinesPagePath = `/:pageName(${SiemPageName.timelines})/:tabName(${TimelineType.default}|${TimelineType.template})`;
-const timelinesDefaultPath = `/${SiemPageName.timelines}/${TimelineType.default}`;
+import { GetUrlForApp } from '../../common/components/navigation/types';
+import { APP_ID } from '../../../common/constants';
+import { SecurityPageName } from '../../app/types';
+
+const timelinesPagePath = `/:tabName(${TimelineType.default}|${TimelineType.template})`;
+const timelinesDefaultPath = `/${TimelineType.default}`;
const TabNameMappedToI18nKey: Record = {
[TimelineType.default]: TAB_TIMELINES,
@@ -30,12 +31,15 @@ const TabNameMappedToI18nKey: Record = {
export const getBreadcrumbs = (
params: TimelineRouteSpyState,
- search: string[]
+ search: string[],
+ getUrlForApp: GetUrlForApp
): ChromeBreadcrumb[] => {
let breadcrumb = [
{
text: PAGE_TITLE,
- href: `${getTimelinesUrl(appendSearch(search[1]))}`,
+ href: getUrlForApp(`${APP_ID}:${SecurityPageName.timelines}`, {
+ path: !isEmpty(search[0]) ? search[0] : '',
+ }),
},
];
@@ -53,16 +57,18 @@ export const getBreadcrumbs = (
};
export const Timelines = React.memo(() => {
+ const history = useHistory();
return (
- {(client) => }
+
(
-
- )}
+ path="/"
+ render={({ location: { search = '' } }) => {
+ history.replace(`${timelinesDefaultPath}${appendSearch(search)}`);
+ return null;
+ }}
/>
);
diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx
index 3a5e3b56e5cc..1bd5874394df 100644
--- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import ApolloClient from 'apollo-client';
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
@@ -21,7 +20,6 @@ jest.mock('../../common/lib/kibana', () => {
});
describe('TimelinesPageComponent', () => {
- const mockAppollloClient = {} as ApolloClient;
let wrapper: ShallowWrapper;
describe('If the user is authorized', () => {
@@ -37,7 +35,7 @@ describe('TimelinesPageComponent', () => {
},
},
});
- wrapper = shallow( );
+ wrapper = shallow( );
});
afterAll(() => {
@@ -89,7 +87,7 @@ describe('TimelinesPageComponent', () => {
},
},
});
- wrapper = shallow( );
+ wrapper = shallow( );
});
afterAll(() => {
diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
index 95a2e4f5fd0e..089a928403b0 100644
--- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
@@ -5,7 +5,6 @@
*/
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import ApolloClient from 'apollo-client';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
@@ -15,6 +14,7 @@ import { HeaderPage } from '../../common/components/header_page';
import { WrapperPage } from '../../common/components/wrapper_page';
import { useKibana } from '../../common/lib/kibana';
import { SpyRoute } from '../../common/utils/route/spy_routes';
+import { useApolloClient } from '../../common/utils/apollo_context';
import { StatefulOpenTimeline } from '../components/open_timeline';
import { NEW_TEMPLATE_TIMELINE } from '../components/timeline/properties/translations';
@@ -22,25 +22,21 @@ import { NewTemplateTimeline } from '../components/timeline/properties/new_templ
import { NewTimeline } from '../components/timeline/properties/helpers';
import * as i18n from './translations';
+import { SecurityPageName } from '../../app/types';
const TimelinesContainer = styled.div`
width: 100%;
`;
-interface TimelinesProps {
- apolloClient: ApolloClient;
-}
-
-type OwnProps = TimelinesProps;
-
export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
-export const TimelinesPageComponent: React.FC = ({ apolloClient }) => {
+export const TimelinesPageComponent: React.FC = () => {
const [importDataModalToggle, setImportDataModalToggle] = useState(false);
const onImportTimelineBtnClick = useCallback(() => {
setImportDataModalToggle(true);
}, [setImportDataModalToggle]);
+ const apolloClient = useApolloClient();
const uiCapabilities = useKibana().services.application.capabilities;
const capabilitiesCanUserCRUD: boolean = !!uiCapabilities.siem.crud;
@@ -87,7 +83,7 @@ export const TimelinesPageComponent: React.FC = ({ apolloClient }) =>
= ({ apolloClient }) =>
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/routes.tsx b/x-pack/plugins/security_solution/public/timelines/routes.tsx
index 50b8e1b8a711..4d1d5f603d21 100644
--- a/x-pack/plugins/security_solution/public/timelines/routes.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/routes.tsx
@@ -5,11 +5,14 @@
*/
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import { Timelines } from './pages';
-import { SiemPageName } from '../app/types';
+import { NotFoundPage } from '../app/404';
-export const getTimelinesRoutes = () => [
- } />,
-];
+export const TimelinesRoutes = () => (
+
+ } />
+ } />
+
+);
diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts
index b6c8e52386cb..f02a6bdcd51e 100644
--- a/x-pack/test/security_solution_endpoint/config.ts
+++ b/x-pack/test/security_solution_endpoint/config.ts
@@ -22,8 +22,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
services,
apps: {
...xpackFunctionalConfig.get('apps'),
- endpoint: {
- pathname: '/app/endpoint',
+ ['securitySolutionManagement']: {
+ pathname: '/app/security/management',
},
},
kbnTestServer: {
diff --git a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
index 830c5ec5a42d..7339903d74a0 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
@@ -17,9 +17,10 @@ export function EndpointPageProvider({ getService, getPageObjects }: FtrProvider
* Navigate to the Endpoints list page
*/
async navigateToEndpointList(searchParams?: string) {
- await pageObjects.common.navigateToApp('securitySolution', {
- hash: `/management/endpoints${searchParams ? `?${searchParams}` : ''}`,
- });
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'securitySolutionManagement',
+ `/endpoints${searchParams ? `?${searchParams}` : ''}`
+ );
await pageObjects.header.waitUntilLoadingHasFinished();
},
diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
index 3ffd7eb032c2..d07c4e70f268 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts
@@ -15,7 +15,10 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
* Navigates to the Endpoint Policy List
*/
async navigateToPolicyList() {
- await pageObjects.common.navigateToApp('securitySolution', { hash: '/management/policy' });
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'securitySolutionManagement',
+ '/policy'
+ );
await pageObjects.header.waitUntilLoadingHasFinished();
},
@@ -51,9 +54,10 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
* @param policyId
*/
async navigateToPolicyDetails(policyId: string) {
- await pageObjects.common.navigateToApp('securitySolution', {
- hash: `/management/policy/${policyId}`,
- });
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'securitySolutionManagement',
+ `/policy/${policyId}`
+ );
await pageObjects.header.waitUntilLoadingHasFinished();
},
From a352b5acf022abc8f1c34da3a86ad40683ebf1c9 Mon Sep 17 00:00:00 2001
From: patrykkopycinski
Date: Sun, 21 Jun 2020 01:23:51 +0200
Subject: [PATCH 26/33] [7.x] Bump jest related packages (#58095) (#69631)
* Bump jest related packages (#58095)
# Conflicts:
# package.json
# packages/kbn-pm/dist/index.js
# packages/kbn-test/package.json
# x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap
# x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_map.test.tsx
# yarn.lock
* Update AgentMarker.test.tsx.snap
---
package.json | 18 +-
packages/eslint-config-kibana/package.json | 2 +-
packages/kbn-dev-utils/package.json | 2 +-
.../serializers/absolute_path_serializer.ts | 2 +-
packages/kbn-optimizer/package.json | 4 +-
.../basic_optimization.test.ts | 2 +-
packages/kbn-pm/dist/index.js | 123 +-
packages/kbn-pm/package.json | 2 +-
.../kbn-pm/src/commands/bootstrap.test.ts | 8 +-
.../strip_ansi_snapshot_serializer.ts | 6 +-
.../__snapshots__/projects_tree.test.ts.snap | 12 +-
.../utils/link_project_executables.test.ts | 6 +-
packages/kbn-spec-to-console/package.json | 2 +-
packages/kbn-storybook/package.json | 2 +-
packages/kbn-test/package.json | 2 +-
.../add_messages_to_report.test.ts | 2 +-
.../form/text_input/text_input.test.js | 20 +-
packages/kbn-ui-shared-deps/package.json | 2 +-
.../application_service.test.mocks.ts | 12 +-
.../application_service.test.tsx | 72 +-
.../collapsible_nav.test.tsx.snap | 32 +-
.../header/__snapshots__/header.test.tsx.snap | 18 +-
.../core_app/errors/url_overflow.test.ts | 20 +-
.../fatal_errors_screen.test.tsx.snap | 2 +-
.../fatal_errors/fatal_errors_screen.test.tsx | 179 +-
.../cluster_client.test.mocks.ts | 13 +-
.../version_check/ensure_es_version.test.ts | 2 +-
src/core/server/http/http_config.test.ts | 12 +-
src/core/server/http/http_tools.test.ts | 13 +-
.../__snapshots__/pattern_layout.test.ts.snap | 12 +-
.../import/validate_references.test.ts | 442 ++--
src/core/server/status/test_utils.ts | 2 +-
.../create_or_upgrade.test.ts | 3 +-
.../strip_ansi_snapshot_serializer.ts | 6 +-
src/dev/jest/cli.js | 4 +-
src/dev/jest/config.js | 1 +
src/dev/jest/setup/react_testing_library.js | 2 +-
.../utils/streams/filter_stream.test.ts | 64 +-
.../field/__snapshots__/field.test.tsx.snap | 44 +-
.../form/__snapshots__/form.test.tsx.snap | 16 +-
.../dashboard_listing.test.js.snap | 20 +-
.../embeddables/embeddable_renderer.test.tsx | 1 -
.../instruction_set.test.js.snap | 20 +-
.../saved_objects_installer.test.js.snap | 12 +-
.../__snapshots__/empty_state.test.tsx.snap | 8 +-
.../__snapshots__/time_field.test.tsx.snap | 20 +-
.../__snapshots__/call_outs.test.tsx.snap | 4 +-
.../bytes/__snapshots__/bytes.test.tsx.snap | 4 +-
.../date/__snapshots__/date.test.tsx.snap | 4 +-
.../__snapshots__/date_nanos.test.tsx.snap | 4 +-
.../number/__snapshots__/number.test.tsx.snap | 4 +-
.../__snapshots__/percent.test.tsx.snap | 4 +-
.../url/__snapshots__/url.test.tsx.snap | 32 +-
.../warning_call_out.test.tsx.snap | 8 +-
.../__snapshots__/flyout.test.tsx.snap | 12 +-
.../saved_objects_table.test.mocks.ts | 22 +-
.../url_panel_content.test.tsx.snap | 32 +-
.../opt_in_message.test.tsx.snap | 4 +-
.../opted_in_notice_banner.test.tsx.snap | 8 +-
.../public/services/telemetry_sender.test.ts | 22 +-
...telemetry_management_section.test.tsx.snap | 8 +-
.../vislib/components/legend/legend.test.tsx | 12 +-
.../public/wizard/new_vis_modal.test.tsx | 15 +-
src/test_utils/public/testbed/types.ts | 12 +-
tsconfig.json | 4 +-
x-pack/dev-tools/jest/create_jest_config.js | 2 +
x-pack/dev-tools/jest/index.js | 4 +-
x-pack/dev-tools/jest/setup/setup_test.js | 1 +
x-pack/package.json | 21 +-
.../app/APMIndicesPermission/index.test.tsx | 23 +-
.../__test__/__snapshots__/List.test.js.snap | 4 +-
.../__test__/ServiceOverview.test.tsx | 4 +-
.../app/Settings/ApmIndices/index.test.tsx | 5 +-
.../CustomLinkFlyout/LinkPreview.test.tsx | 4 +-
.../CustomizeUI/CustomLink/index.test.tsx | 31 +-
.../app/TraceLink/__test__/TraceLink.test.tsx | 18 +-
.../StickyProperties.test.js.snap | 16 +-
.../__test__/TransactionActionMenu.test.tsx | 7 +-
.../__snapshots__/CustomPlot.test.js.snap | 16 +-
.../__snapshots__/AgentMarker.test.tsx.snap | 8 +-
.../plugins/apm/public/utils/testHelpers.tsx | 1 -
.../shape_picker.stories.storyshot | 4 +-
.../shape_picker_popover.stories.storyshot | 4 +-
.../shape_preview.stories.storyshot | 4 +-
.../__snapshots__/shareable.test.tsx.snap | 30 +-
.../__tests__/__snapshots__/app.test.tsx.snap | 6 +-
.../mocks/breadcrumbs.mock.ts | 12 +-
.../mocks/track_ui_metric.mock.ts | 18 +-
.../flyout_edit_drilldown/menu_item.test.tsx | 1 -
.../services/search_service.test.mocks.ts | 12 +-
.../extend_index_management.test.js.snap | 4 +-
.../template_clone.test.tsx | 48 +-
.../template_create.test.tsx | 48 +-
.../template_edit.test.tsx | 48 +-
.../datatypes/text_datatype.test.tsx | 8 +-
.../client_integration/edit_field.test.tsx | 2 +-
.../helpers/mappings_editor.helpers.tsx | 74 +-
.../load_mappings_provider.test.tsx | 42 +-
.../ingest_pipelines_clone.test.tsx | 28 +-
.../ingest_pipelines_create.test.tsx | 28 +-
.../ingest_pipelines_edit.test.tsx | 28 +-
.../pipeline_processors_editor.helpers.tsx | 70 +-
.../__jest__/upload_license.test.tsx | 10 +-
.../dynamic_size_property.test.tsx.snap | 4 +-
.../__snapshots__/view.test.js.snap | 2 +-
.../rule_editor_flyout.test.js.snap | 12 +-
.../rule_action_panel.test.js.snap | 12 +-
.../validate_job_view.test.js.snap | 8 +-
.../__snapshots__/overrides.test.js.snap | 4 +-
.../explorer/explorer_swimlane.test.tsx | 11 +
.../list/__snapshots__/header.test.js.snap | 4 +-
.../logs/__snapshots__/logs.test.js.snap | 16 +-
.../logs/__snapshots__/reason.test.js.snap | 32 +-
.../flyout/__snapshots__/flyout.test.js.snap | 104 +-
.../plugins/monitoring/server/config.test.ts | 12 +-
.../remote_cluster_form.test.js.snap | 16 +-
.../access_agreement_page.test.tsx | 4 -
.../components/login_form/login_form.test.tsx | 4 -
.../authentication/login/login_page.test.tsx | 4 -
.../authentication/logout/logout_app.test.ts | 5 -
.../api_keys_grid_page.test.tsx.snap | 4 +-
.../elasticsearch_privileges.test.tsx.snap | 8 +-
.../nav_control_component.test.tsx | 8 +-
.../public/session/session_expired.test.ts | 23 +-
.../load_empty_prompt.test.tsx | 16 +-
.../__snapshots__/index.test.tsx.snap | 2 +-
.../detection_engine/rules/all/index.test.tsx | 16 +-
.../rules/create/index.test.tsx | 16 +-
.../detection_engine/rules/index.test.tsx | 16 +-
.../components/all_cases_modal/index.test.tsx | 16 +-
.../components/case_view/actions.test.tsx | 16 +-
.../configure_cases/button.test.tsx | 16 +-
.../use_push_to_service/index.test.tsx | 16 +-
.../common/components/charts/common.test.tsx | 4 +-
.../__snapshots__/index.test.tsx.snap | 4 +-
.../components/header_page/index.test.tsx | 16 +-
.../__snapshots__/anomaly_score.test.tsx.snap | 26 +-
.../popover_description.test.tsx.snap | 4 +-
.../upgrade_contents.test.tsx.snap | 4 +-
.../components/navigation/index.test.tsx | 16 +-
.../navigation/tab_navigation/index.test.tsx | 16 +-
.../super_date_picker/index.test.tsx | 38 -
.../common/components/top_n/index.test.tsx | 16 +-
.../common/components/top_n/top_n.test.tsx | 16 +-
.../flow_target_select.test.tsx.snap | 16 +-
.../source_destination/index.test.tsx | 18 +-
.../__snapshots__/index.test.tsx.snap | 16 +-
.../__snapshots__/index.test.tsx.snap | 12 +-
.../components/fields_browser/index.test.tsx | 2 +-
.../header_with_close_button/index.test.tsx | 42 +-
.../__snapshots__/new_note.test.tsx.snap | 8 +-
.../body/column_headers/header/index.test.tsx | 2 +-
.../components/timeline/body/index.test.tsx | 2 +-
.../system/generic_file_details.test.tsx | 18 +-
.../components/timeline/index.test.tsx | 12 +-
.../timeline/properties/index.test.tsx | 36 +-
.../__jest__/client_integration/home.test.ts | 4 +-
.../space_identifier.test.tsx.snap | 4 +-
.../enabled_features.test.tsx.snap | 4 +-
.../nav_control_popover.test.tsx.snap | 4 +-
.../public/app/hooks/use_index_data.test.tsx | 1 -
.../step_create/step_create_form.test.tsx | 1 -
.../step_define/step_define_form.test.tsx | 1 -
.../step_define/step_define_summary.test.tsx | 1 -
.../components/health_check.test.tsx | 1 -
.../lib/check_action_type_enabled.test.tsx | 106 +-
.../action_wizard/action_wizard.test.tsx | 1 -
...onnected_flyout_manage_drilldowns.test.tsx | 1 -
.../list_manage_drilldowns.test.tsx | 1 -
.../__snapshots__/checkup_tab.test.tsx.snap | 16 +-
.../lib/reindexing/reindex_actions.test.ts | 6 +-
.../__snapshots__/ml_flyout.test.tsx.snap | 4 +-
.../__snapshots__/doc_link_body.test.tsx.snap | 4 +-
.../__snapshots__/ping_list.test.tsx.snap | 4 +-
.../data_or_index_missing.test.tsx.snap | 4 +-
.../__snapshots__/empty_state.test.tsx.snap | 12 +-
.../__tests__/filter_popover.test.tsx | 2 +-
.../watch_create_threshold.test.tsx | 60 +-
.../client_integration/watch_edit.test.ts | 28 +-
x-pack/test_utils/jest/config.js | 2 +
x-pack/test_utils/testbed/types.ts | 14 +-
x-pack/tsconfig.json | 4 +-
x-pack/typings/jest.d.ts | 20 -
yarn.lock | 1923 +++++++++++------
184 files changed, 2981 insertions(+), 2201 deletions(-)
diff --git a/package.json b/package.json
index cddeb28fcdfa..5d4c165c5589 100644
--- a/package.json
+++ b/package.json
@@ -256,7 +256,7 @@
"reselect": "^4.0.0",
"resize-observer-polyfill": "^1.5.0",
"rison-node": "1.0.2",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"script-loader": "0.7.2",
"seedrandom": "^3.0.5",
"semver": "^5.5.0",
@@ -341,7 +341,7 @@
"@types/history": "^4.7.3",
"@types/hoek": "^4.1.3",
"@types/inert": "^5.1.2",
- "@types/jest": "24.0.19",
+ "@types/jest": "^25.2.3",
"@types/joi": "^13.4.2",
"@types/jquery": "^3.3.31",
"@types/js-yaml": "^3.11.1",
@@ -386,6 +386,7 @@
"@types/supertest-as-promised": "^2.0.38",
"@types/tar": "^4.0.3",
"@types/testing-library__dom": "^6.10.0",
+ "@types/testing-library__jest-dom": "^5.7.0",
"@types/testing-library__react": "^9.1.2",
"@types/testing-library__react-hooks": "^3.1.0",
"@types/type-detect": "^4.0.1",
@@ -398,8 +399,9 @@
"archiver": "^3.1.1",
"axe-core": "^3.4.1",
"babel-eslint": "^10.0.3",
- "babel-jest": "^24.9.0",
- "backport": "4.9.0",
+ "babel-jest": "^25.5.1",
+ "babel-plugin-istanbul": "^6.0.0",
+ "backport": "5.4.1",
"chai": "3.5.0",
"chance": "1.0.18",
"cheerio": "0.22.0",
@@ -417,7 +419,7 @@
"eslint-plugin-ban": "^1.4.0",
"eslint-plugin-cypress": "^2.8.1",
"eslint-plugin-import": "^2.19.1",
- "eslint-plugin-jest": "^23.3.0",
+ "eslint-plugin-jest": "^23.10.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-mocha": "^6.2.2",
"eslint-plugin-no-unsanitized": "^3.0.2",
@@ -446,8 +448,10 @@
"intl-messageformat-parser": "^1.4.0",
"is-path-inside": "^2.1.0",
"istanbul-instrumenter-loader": "3.0.1",
- "jest": "^24.9.0",
- "jest-cli": "^24.9.0",
+ "jest": "^25.5.4",
+ "jest-environment-jsdom-thirteen": "^1.0.1",
+ "jest-circus": "^25.5.4",
+ "jest-cli": "^25.5.4",
"jest-raw-loader": "^1.0.1",
"jimp": "^0.9.6",
"json5": "^1.0.1",
diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json
index 9bc8ed3019e5..e14423d681a4 100644
--- a/packages/eslint-config-kibana/package.json
+++ b/packages/eslint-config-kibana/package.json
@@ -23,7 +23,7 @@
"eslint-plugin-ban": "^1.4.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-import": "^2.19.1",
- "eslint-plugin-jest": "^23.3.0",
+ "eslint-plugin-jest": "^23.10.0",
"eslint-plugin-mocha": "^6.2.2",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json
index dedc2707f5b5..d95cd1d404a1 100644
--- a/packages/kbn-dev-utils/package.json
+++ b/packages/kbn-dev-utils/package.json
@@ -18,7 +18,7 @@
"getopts": "^2.2.5",
"load-json-file": "^6.2.0",
"moment": "^2.24.0",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"tree-kill": "^1.2.2",
"tslib": "^2.0.0"
},
diff --git a/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.ts b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.ts
index 9edc63dd7d84..af55622c7619 100644
--- a/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.ts
+++ b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.ts
@@ -21,7 +21,7 @@ import { REPO_ROOT } from '../repo_root';
export function createAbsolutePathSerializer(rootPath: string = REPO_ROOT) {
return {
- print: (value: string) => value.replace(rootPath, '').replace(/\\/g, '/'),
+ serialize: (value: string) => value.replace(rootPath, '').replace(/\\/g, '/'),
test: (value: any) => typeof value === 'string' && value.startsWith(rootPath),
};
}
diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json
index c7bf1dd60985..c11bd1b64693 100644
--- a/packages/kbn-optimizer/package.json
+++ b/packages/kbn-optimizer/package.json
@@ -31,7 +31,7 @@
"execa": "^4.0.2",
"file-loader": "^4.2.0",
"istanbul-instrumenter-loader": "^3.0.1",
- "jest-diff": "^25.1.0",
+ "jest-diff": "^25.5.0",
"json-stable-stringify": "^1.0.1",
"loader-utils": "^1.2.3",
"node-sass": "^4.13.0",
@@ -39,7 +39,7 @@
"postcss-loader": "^3.0.0",
"raw-loader": "^3.1.0",
"resolve-url-loader": "^3.1.1",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.1.2",
diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
index efe3f45d6512..4410ffe81c0d 100644
--- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
+++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
@@ -33,7 +33,7 @@ const MOCK_REPO_SRC = Path.resolve(__dirname, '../__fixtures__/mock_repo');
const MOCK_REPO_DIR = Path.resolve(TMP_DIR, 'mock_repo');
expect.addSnapshotSerializer({
- print: (value: string) => value.split(REPO_ROOT).join('').replace(/\\/g, '/'),
+ serialize: (value: string) => value.split(REPO_ROOT).join('').replace(/\\/g, '/'),
test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT),
});
diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js
index 86bd9716c154..d2b3c1c1ec7c 100644
--- a/packages/kbn-pm/dist/index.js
+++ b/packages/kbn-pm/dist/index.js
@@ -986,10 +986,10 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(24);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__["pipe"]; });
-/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(25);
+/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(60);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__["noop"]; });
-/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(60);
+/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(25);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__["identity"]; });
/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(61);
@@ -2163,8 +2163,8 @@ var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return pipe; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipeFromArray", function() { return pipeFromArray; });
-/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(25);
-/** PURE_IMPORTS_START _noop PURE_IMPORTS_END */
+/* harmony import */ var _identity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(25);
+/** PURE_IMPORTS_START _identity PURE_IMPORTS_END */
function pipe() {
var fns = [];
@@ -2174,8 +2174,8 @@ function pipe() {
return pipeFromArray(fns);
}
function pipeFromArray(fns) {
- if (!fns) {
- return _noop__WEBPACK_IMPORTED_MODULE_0__["noop"];
+ if (fns.length === 0) {
+ return _identity__WEBPACK_IMPORTED_MODULE_0__["identity"];
}
if (fns.length === 1) {
return fns[0];
@@ -2193,10 +2193,12 @@ function pipeFromArray(fns) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return noop; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return identity; });
/** PURE_IMPORTS_START PURE_IMPORTS_END */
-function noop() { }
-//# sourceMappingURL=noop.js.map
+function identity(x) {
+ return x;
+}
+//# sourceMappingURL=identity.js.map
/***/ }),
@@ -3848,26 +3850,34 @@ var AsapAction = /*@__PURE__*/ (function (_super) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Immediate", function() { return Immediate; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TestTools", function() { return TestTools; });
/** PURE_IMPORTS_START PURE_IMPORTS_END */
var nextHandle = 1;
-var tasksByHandle = {};
-function runIfPresent(handle) {
- var cb = tasksByHandle[handle];
- if (cb) {
- cb();
+var RESOLVED = /*@__PURE__*/ (function () { return /*@__PURE__*/ Promise.resolve(); })();
+var activeHandles = {};
+function findAndClearHandle(handle) {
+ if (handle in activeHandles) {
+ delete activeHandles[handle];
+ return true;
}
+ return false;
}
var Immediate = {
setImmediate: function (cb) {
var handle = nextHandle++;
- tasksByHandle[handle] = cb;
- Promise.resolve().then(function () { return runIfPresent(handle); });
+ activeHandles[handle] = true;
+ RESOLVED.then(function () { return findAndClearHandle(handle) && cb(); });
return handle;
},
clearImmediate: function (handle) {
- delete tasksByHandle[handle];
+ findAndClearHandle(handle);
},
};
+var TestTools = {
+ pending: function () {
+ return Object.keys(activeHandles).length;
+ }
+};
//# sourceMappingURL=Immediate.js.map
@@ -4169,12 +4179,10 @@ var VirtualAction = /*@__PURE__*/ (function (_super) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return identity; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return noop; });
/** PURE_IMPORTS_START PURE_IMPORTS_END */
-function identity(x) {
- return x;
-}
-//# sourceMappingURL=identity.js.map
+function noop() { }
+//# sourceMappingURL=noop.js.map
/***/ }),
@@ -4728,17 +4736,17 @@ __webpack_require__.r(__webpack_exports__);
-function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) {
- if (destination === void 0) {
- destination = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex);
+function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, innerSubscriber) {
+ if (innerSubscriber === void 0) {
+ innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex);
}
- if (destination.closed) {
+ if (innerSubscriber.closed) {
return undefined;
}
if (result instanceof _Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"]) {
- return result.subscribe(destination);
+ return result.subscribe(innerSubscriber);
}
- return Object(_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(result)(destination);
+ return Object(_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(result)(innerSubscriber);
}
//# sourceMappingURL=subscribeToResult.js.map
@@ -5010,7 +5018,7 @@ function concatAll() {
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return mergeAll; });
/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(82);
-/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(60);
+/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
/** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */
@@ -5108,10 +5116,13 @@ var MergeMapSubscriber = /*@__PURE__*/ (function (_super) {
this._innerSub(result, value, index);
};
MergeMapSubscriber.prototype._innerSub = function (ish, value, index) {
- var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined);
+ var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, value, index);
var destination = this.destination;
destination.add(innerSubscriber);
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ destination.add(innerSubscription);
+ }
};
MergeMapSubscriber.prototype._complete = function () {
this.hasCompleted = true;
@@ -5607,7 +5618,7 @@ function fromEventPattern(addHandler, removeHandler, resultSelector) {
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return generate; });
/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
-/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(60);
+/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(45);
/** PURE_IMPORTS_START _Observable,_util_identity,_util_isScheduler PURE_IMPORTS_END */
@@ -5866,7 +5877,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return NEVER; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "never", function() { return never; });
/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
-/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
+/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(60);
/** PURE_IMPORTS_START _Observable,_util_noop PURE_IMPORTS_END */
@@ -51259,7 +51270,10 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) {
this._unsubscribeAndRecycle();
var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined);
this.add(innerSubscriber);
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ this.add(innerSubscription);
+ }
}
};
return CatchSubscriber;
@@ -52483,10 +52497,13 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) {
this._innerSub(result, value, index);
};
ExhaustMapSubscriber.prototype._innerSub = function (result, value, index) {
- var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined);
+ var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, value, index);
var destination = this.destination;
destination.add(innerSubscriber);
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ destination.add(innerSubscription);
+ }
};
ExhaustMapSubscriber.prototype._complete = function () {
this.hasCompleted = true;
@@ -52771,7 +52788,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420);
/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(410);
/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(419);
-/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(60);
+/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25);
/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */
@@ -52879,7 +52896,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(433);
/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(419);
/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(410);
-/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(60);
+/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25);
/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */
@@ -53305,10 +53322,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) {
}
};
MergeScanSubscriber.prototype._innerSub = function (ish, value, index) {
- var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined);
+ var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, value, index);
var destination = this.destination;
destination.add(innerSubscriber);
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ destination.add(innerSubscription);
+ }
};
MergeScanSubscriber.prototype._complete = function () {
this.hasCompleted = true;
@@ -53495,7 +53515,10 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) {
var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__["InnerSubscriber"](this, undefined, undefined);
var destination = this.destination;
destination.add(innerSubscriber);
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, next, undefined, undefined, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, next, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ destination.add(innerSubscription);
+ }
}
else {
this.destination.complete();
@@ -54333,6 +54356,7 @@ function shareReplayOperator(_a) {
},
complete: function () {
isComplete = true;
+ subscription = undefined;
subject.complete();
},
});
@@ -54572,7 +54596,11 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) {
var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](_this, undefined, undefined);
_this.add(innerSubscriber);
_this.innerSubscription = innerSubscriber;
- Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(_this, notifier, undefined, undefined, innerSubscriber);
+ var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(_this, notifier, undefined, undefined, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ _this.add(innerSubscription);
+ _this.innerSubscription = innerSubscription;
+ }
return _this;
}
SkipUntilSubscriber.prototype._next = function (value) {
@@ -54781,7 +54809,7 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) {
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; });
/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(471);
-/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(60);
+/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */
@@ -54851,10 +54879,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) {
if (innerSubscription) {
innerSubscription.unsubscribe();
}
- var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined);
+ var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, value, index);
var destination = this.destination;
destination.add(innerSubscriber);
- this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber);
+ this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber);
+ if (this.innerSubscription !== innerSubscriber) {
+ destination.add(this.innerSubscription);
+ }
};
SwitchMapSubscriber.prototype._complete = function () {
var innerSubscription = this.innerSubscription;
@@ -55025,7 +55056,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; });
/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12);
/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
-/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(25);
+/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(60);
/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13);
/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */
diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json
index d9ff0be55bde..3e7ed49c6131 100644
--- a/packages/kbn-pm/package.json
+++ b/packages/kbn-pm/package.json
@@ -53,7 +53,7 @@
"ora": "^1.4.0",
"prettier": "^2.0.5",
"read-pkg": "^5.2.0",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"spawn-sync": "^1.0.15",
"string-replace-loader": "^2.2.0",
"strip-ansi": "^4.0.0",
diff --git a/packages/kbn-pm/src/commands/bootstrap.test.ts b/packages/kbn-pm/src/commands/bootstrap.test.ts
index c69427e8b3c7..97505a66a1ff 100644
--- a/packages/kbn-pm/src/commands/bootstrap.test.ts
+++ b/packages/kbn-pm/src/commands/bootstrap.test.ts
@@ -127,13 +127,13 @@ test('handles dependencies of dependencies', async () => {
expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir');
expect(logWriter.messages).toMatchInlineSnapshot(`
Array [
- " info [kibana] running yarn",
+ info [kibana] running yarn,
"",
"",
- " info [bar] running yarn",
+ info [bar] running yarn,
"",
"",
- " info [foo] running yarn",
+ info [foo] running yarn,
"",
"",
]
@@ -174,7 +174,7 @@ test('does not run installer if no deps in package', async () => {
expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir');
expect(logWriter.messages).toMatchInlineSnapshot(`
Array [
- " info [kibana] running yarn",
+ info [kibana] running yarn,
"",
"",
]
diff --git a/packages/kbn-pm/src/test_helpers/strip_ansi_snapshot_serializer.ts b/packages/kbn-pm/src/test_helpers/strip_ansi_snapshot_serializer.ts
index bf8bd129c0bb..d5b73dfd4d5e 100644
--- a/packages/kbn-pm/src/test_helpers/strip_ansi_snapshot_serializer.ts
+++ b/packages/kbn-pm/src/test_helpers/strip_ansi_snapshot_serializer.ts
@@ -20,9 +20,9 @@
import hasAnsi from 'has-ansi';
import stripAnsi from 'strip-ansi';
-export const stripAnsiSnapshotSerializer = {
- print(value: string, serialize: (val: string) => string) {
- return serialize(stripAnsi(value));
+export const stripAnsiSnapshotSerializer: jest.SnapshotSerializerPlugin = {
+ serialize(value: string) {
+ return stripAnsi(value);
},
test(value: any) {
diff --git a/packages/kbn-pm/src/utils/__snapshots__/projects_tree.test.ts.snap b/packages/kbn-pm/src/utils/__snapshots__/projects_tree.test.ts.snap
index 12cbc704b480..9993e2eaf377 100644
--- a/packages/kbn-pm/src/utils/__snapshots__/projects_tree.test.ts.snap
+++ b/packages/kbn-pm/src/utils/__snapshots__/projects_tree.test.ts.snap
@@ -1,29 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`handles projects outside root folder 1`] = `
-"kibana
+kibana
├── packages
│ ├── bar
│ └── foo
└── ../plugins
├── baz
├── quux
- └── zorge"
+ └── zorge
`;
exports[`handles projects with root folder 1`] = `
-"kibana
+kibana
└── packages
├── bar
- └── foo"
+ └── foo
`;
exports[`handles projects within projects outside root folder 1`] = `
-"kibana
+kibana
├── packages
│ ├── bar
│ └── foo
└── ../kibana-extra/additional_projects (with-additional-projects)
├── packages/baz
- └── plugins/quux"
+ └── plugins/quux
`;
diff --git a/packages/kbn-pm/src/utils/link_project_executables.test.ts b/packages/kbn-pm/src/utils/link_project_executables.test.ts
index f5c19d569f6b..a98d2caa3034 100644
--- a/packages/kbn-pm/src/utils/link_project_executables.test.ts
+++ b/packages/kbn-pm/src/utils/link_project_executables.test.ts
@@ -115,9 +115,9 @@ describe('bin script points to a file', () => {
expect(getFsMockCalls()).toMatchSnapshot('fs module calls');
expect(logWriter.messages).toMatchInlineSnapshot(`
Array [
- " debg Linking package executables",
- " debg [foo] bar -> ../bar/bin/bar.js",
- " debg [baz] bar -> ../bar/bin/bar.js",
+ debg Linking package executables,
+ debg [foo] bar -> ../bar/bin/bar.js,
+ debg [baz] bar -> ../bar/bin/bar.js,
]
`);
});
diff --git a/packages/kbn-spec-to-console/package.json b/packages/kbn-spec-to-console/package.json
index 6bb9945af8b3..a1e73d985ae2 100644
--- a/packages/kbn-spec-to-console/package.json
+++ b/packages/kbn-spec-to-console/package.json
@@ -17,7 +17,7 @@
},
"homepage": "https://github.com/jbudz/spec-to-console#readme",
"devDependencies": {
- "jest": "^24.9.0",
+ "jest": "^25.5.4",
"prettier": "^2.0.5"
},
"dependencies": {
diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json
index 72308fa31287..4f035c3334b8 100644
--- a/packages/kbn-storybook/package.json
+++ b/packages/kbn-storybook/package.json
@@ -23,7 +23,7 @@
"mini-css-extract-plugin": "0.7.0",
"normalize-path": "3.0.0",
"react-docgen-typescript-loader": "3.1.0",
- "rxjs": "6.5.2",
+ "rxjs": "6.5.5",
"serve-static": "1.14.1",
"styled-components": "^5.1.0",
"webpack": "^4.41.5"
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index c74dba8a34c9..2ab27f048eda 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -28,7 +28,7 @@
"parse-link-header": "^1.0.1",
"puppeteer": "^3.3.0",
"strip-ansi": "^5.2.0",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"tar-fs": "^1.16.3",
"tmp": "^0.1.0",
"xml2js": "^0.4.22",
diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts
index 7cbeb18a5ebd..f8f279151e07 100644
--- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts
@@ -26,7 +26,7 @@ import { createPatch } from 'diff';
// turns out Jest can't encode xml diffs in their JUnit reports...
expect.addSnapshotSerializer({
test: (v) => typeof v === 'string' && (v.includes('<') || v.includes('>')),
- print: (v) => v.replace(//g, '›').replace(/^\s+$/gm, ''),
+ serialize: (v) => v.replace(//g, '›').replace(/^\s+$/gm, ''),
});
jest.mock('fs', () => {
diff --git a/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js b/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js
index 41d3726582fb..2e614c3f1761 100644
--- a/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js
+++ b/packages/kbn-ui-framework/src/components/form/text_input/text_input.test.js
@@ -45,18 +45,34 @@ describe('KuiTextInput', () => {
});
describe('autoFocus', () => {
+ /* eslint-disable no-console */
+ // Silence until enzyme fixed https://github.com/enzymejs/enzyme/issues/2337
+ const originalError = console.error;
+ beforeAll(() => {
+ console.error = jest.fn();
+ });
+ afterAll(() => {
+ console.error = originalError;
+ });
+ /* eslint-enable no-console */
+
test('sets focus on the element', () => {
const component = mount(
- {}} data-test-subj="input" />
+ {}} data-test-subj="input" />,
+ { attachTo: document.body }
);
expect(findTestSubject(component, 'input').getDOMNode()).toBe(document.activeElement);
+ component.unmount();
});
test('does not focus the element by default', () => {
- const component = mount( {}} data-test-subj="input" />);
+ const component = mount( {}} data-test-subj="input" />, {
+ attachTo: document.body,
+ });
expect(findTestSubject(component, 'input').getDOMNode()).not.toBe(document.activeElement);
+ component.unmount();
});
});
diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json
index 0cfbec434912..9ea006cff284 100644
--- a/packages/kbn-ui-shared-deps/package.json
+++ b/packages/kbn-ui-shared-deps/package.json
@@ -29,7 +29,7 @@
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"regenerator-runtime": "^0.13.3",
- "rxjs": "^6.5.3",
+ "rxjs": "^6.5.5",
"symbol-observable": "^1.2.0",
"whatwg-fetch": "^3.0.0"
},
diff --git a/src/core/public/application/application_service.test.mocks.ts b/src/core/public/application/application_service.test.mocks.ts
index 727295237d74..11c69c1971fd 100644
--- a/src/core/public/application/application_service.test.mocks.ts
+++ b/src/core/public/application/application_service.test.mocks.ts
@@ -37,7 +37,11 @@ jest.doMock('history', () => ({
}));
export const parseAppUrlMock = jest.fn();
-jest.doMock('./utils', () => ({
- ...jest.requireActual('./utils'),
- parseAppUrl: parseAppUrlMock,
-}));
+jest.doMock('./utils', () => {
+ const original = jest.requireActual('./utils');
+
+ return {
+ ...original,
+ parseAppUrl: parseAppUrlMock,
+ };
+});
diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx
index e3cd0761c324..b0419d276dfa 100644
--- a/src/core/public/application/integration_tests/application_service.test.tsx
+++ b/src/core/public/application/integration_tests/application_service.test.tsx
@@ -147,44 +147,52 @@ describe('ApplicationService', () => {
});
});
- it('redirects to full path when navigating to legacy app', async () => {
- const redirectTo = jest.fn();
- const reloadSpy = jest.spyOn(window.location, 'reload').mockImplementation(() => {});
-
- // In the real application, we use a BrowserHistory instance configured with `basename`. However, in tests we must
- // use MemoryHistory which does not support `basename`. In order to emulate this behavior, we will wrap this
- // instance with a ScopedHistory configured with a basepath.
- history.push(setupDeps.http.basePath.get()); // ScopedHistory constructor will fail if underlying history is not currently at basePath.
- const { register, registerLegacyApp } = service.setup({
- ...setupDeps,
- redirectTo,
- history: new ScopedHistory(history, setupDeps.http.basePath.get()),
+ describe('redirects', () => {
+ beforeAll(() => {
+ Object.defineProperty(window, 'location', {
+ value: {
+ reload: jest.fn(),
+ },
+ });
});
- register(Symbol(), {
- id: 'app1',
- title: 'App1',
- mount: ({ onAppLeave }: AppMountParameters) => {
- onAppLeave((actions) => actions.default());
- return () => undefined;
- },
- });
- registerLegacyApp({
- id: 'myLegacyTestApp',
- appUrl: '/app/myLegacyTestApp',
- title: 'My Legacy Test App',
- });
+ it('to full path when navigating to legacy app', async () => {
+ const redirectTo = jest.fn();
+
+ // In the real application, we use a BrowserHistory instance configured with `basename`. However, in tests we must
+ // use MemoryHistory which does not support `basename`. In order to emulate this behavior, we will wrap this
+ // instance with a ScopedHistory configured with a basepath.
+ history.push(setupDeps.http.basePath.get()); // ScopedHistory constructor will fail if underlying history is not currently at basePath.
+ const { register, registerLegacyApp } = service.setup({
+ ...setupDeps,
+ redirectTo,
+ history: new ScopedHistory(history, setupDeps.http.basePath.get()),
+ });
+
+ register(Symbol(), {
+ id: 'app1',
+ title: 'App1',
+ mount: ({ onAppLeave }: AppMountParameters) => {
+ onAppLeave((actions) => actions.default());
+ return () => undefined;
+ },
+ });
+ registerLegacyApp({
+ id: 'myLegacyTestApp',
+ appUrl: '/app/myLegacyTestApp',
+ title: 'My Legacy Test App',
+ });
- const { navigateToApp, getComponent } = await service.start(startDeps);
+ const { navigateToApp, getComponent } = await service.start(startDeps);
- update = createRenderer(getComponent());
+ update = createRenderer(getComponent());
- await navigate('/test/app/app1');
- await act(() => navigateToApp('myLegacyTestApp', { path: '#/some-path' }));
+ await navigate('/test/app/app1');
+ await act(() => navigateToApp('myLegacyTestApp', { path: '#/some-path' }));
- expect(redirectTo).toHaveBeenCalledWith('/test/app/myLegacyTestApp#/some-path');
- expect(reloadSpy).toHaveBeenCalled();
- reloadSpy.mockRestore();
+ expect(redirectTo).toHaveBeenCalledWith('/test/app/myLegacyTestApp#/some-path');
+ expect(window.location.reload).toHaveBeenCalled();
+ });
});
describe('leaving an application that registered an app leave handler', () => {
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
index 60963c0acb99..9239811df206 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
@@ -410,6 +410,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
>
"
`;
diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts
index 60a196254d40..0c5a7ca79e27 100644
--- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts
+++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts
@@ -4,7 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-jest.mock('../../../app/services/breadcrumbs', () => ({
- ...jest.requireActual('../../../app/services/breadcrumbs'),
- setBreadcrumbs: jest.fn(),
-}));
+jest.mock('../../../app/services/breadcrumbs', () => {
+ const original = jest.requireActual('../../../app/services/breadcrumbs');
+
+ return {
+ ...original,
+ setBreadcrumbs: jest.fn(),
+ };
+});
diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts
index 5460b482cd57..46cfda8cc672 100644
--- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts
+++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts
@@ -4,10 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-jest.mock('../../../app/services/track_ui_metric', () => ({
- ...jest.requireActual('../../../app/services/track_ui_metric'),
- trackUiMetric: jest.fn(),
- trackUserRequest: (request: Promise) => {
- return request.then((response) => response);
- },
-}));
+jest.mock('../../../app/services/track_ui_metric', () => {
+ const original = jest.requireActual('../../../app/services/track_ui_metric');
+
+ return {
+ ...original,
+ trackUiMetric: jest.fn(),
+ trackUserRequest: (request: Promise) => {
+ return request.then((response) => response);
+ },
+ };
+});
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
index 9a4ecb2d4bfb..771b15e46ad2 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
@@ -10,7 +10,6 @@ import { MenuItem } from './menu_item';
import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/public';
import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../ui_actions_enhanced/public';
import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public';
-import '@testing-library/jest-dom';
afterEach(cleanup);
diff --git a/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts b/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts
index ce406e27c4a7..1caabd6a1681 100644
--- a/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts
+++ b/x-pack/plugins/global_search/public/services/search_service.test.mocks.ts
@@ -10,7 +10,11 @@ jest.doMock('./fetch_server_results', () => ({
}));
export const getDefaultPreferenceMock = jest.fn();
-jest.doMock('./utils', () => ({
- ...jest.requireActual('./utils'),
- getDefaultPreference: getDefaultPreferenceMock,
-}));
+jest.doMock('./utils', () => {
+ const original = jest.requireActual('./utils');
+
+ return {
+ ...original,
+ getDefaultPreference: getDefaultPreferenceMock,
+ };
+});
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap
index 9441ffd73152..38dd49a286b5 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap
@@ -495,7 +495,7 @@ exports[`extend index management ilm summary extension should return extension w
-
+
}
closePopover={[Function]}
display="inlineBlock"
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
index 6250ef0dc247..ccb729db44f9 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
@@ -14,28 +14,32 @@ import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } fro
import { setup } from './template_clone.helpers';
import { TemplateFormTestBed } from './template_form.helpers';
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
// FLAKY: https://github.com/elastic/kibana/issues/59849
describe.skip(' ', () => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
index 50b35fc76721..07a27e2414ae 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
@@ -20,28 +20,32 @@ import {
import { setup } from './template_create.helpers';
import { TemplateFormTestBed } from './template_form.helpers';
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
const TEXT_MAPPING_FIELD = {
name: 'text_datatype',
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
index 88067d479f7e..9f0e81454f0a 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
@@ -25,28 +25,32 @@ const MAPPING = {
},
};
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const origial = jest.requireActual('@elastic/eui');
+
+ return {
+ ...origial,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
// FLAKY: https://github.com/elastic/kibana/issues/65567
describe.skip(' ', () => {
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx
index 5f6d3a6e7b45..ed60414d198f 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx
@@ -82,7 +82,7 @@ describe.skip('Mappings editor: text datatype', () => {
({ data } = await getMappingsEditorData(component));
expect(data).toEqual(updatedMappings);
- });
+ }, 10000);
test('analyzer parameter: default values', async () => {
const defaultMappings = {
@@ -210,7 +210,7 @@ describe.skip('Mappings editor: text datatype', () => {
expect(indexAnalyzerValue).toBe('standard');
expect(searchAnalyzerValue).toBe('simple');
expect(searchQuoteAnalyzerValue).toBe('whitespace');
- }, 10000);
+ }, 50000);
test('analyzer parameter: custom analyzer (external plugin)', async () => {
const defaultMappings = {
@@ -303,7 +303,7 @@ describe.skip('Mappings editor: text datatype', () => {
};
expect(data).toEqual(updatedMappings);
- });
+ }, 100000);
test('analyzer parameter: custom analyzer (from index settings)', async () => {
const indexSettings = {
@@ -394,5 +394,5 @@ describe.skip('Mappings editor: text datatype', () => {
};
expect(data).toEqual(updatedMappings);
- });
+ }, 50000);
});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx
index d6d1510f1200..4f9d8a960a1a 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx
@@ -109,5 +109,5 @@ describe('Mappings editor: edit field', () => {
};
expect(data).toEqual(updatedMappings);
- });
+ }, 50000);
});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
index bef2d5c79be9..638bbfd925ff 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
@@ -11,41 +11,45 @@ import { registerTestBed, TestBed } from '../../../../../../../../../test_utils'
import { getChildFieldsName } from '../../../lib';
import { MappingsEditor } from '../../../mappings_editor';
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(e.jsonContent);
- }}
- />
- ),
- // Mocking EuiSuperSelect to be able to easily change its value
- // with a `myWrapper.simulate('change', { target: { value: 'someValue' } })`
- EuiSuperSelect: (props: any) => (
- {
- props.onChange(e.target.value);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(e.jsonContent);
+ }}
+ />
+ ),
+ // Mocking EuiSuperSelect to be able to easily change its value
+ // with a `myWrapper.simulate('change', { target: { value: 'someValue' } })`
+ EuiSuperSelect: (props: any) => (
+ {
+ props.onChange(e.target.value);
+ }}
+ />
+ ),
+ };
+});
export interface DomFields {
[key: string]: {
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
index 144a82c82a4f..49c4064222e3 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
@@ -6,23 +6,31 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
-
-jest.mock('lodash', () => ({
- ...jest.requireActual('lodash'),
- debounce: (fn: any) => fn,
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
+
+jest.mock('lodash', () => {
+ const original = jest.requireActual('lodash');
+
+ return {
+ ...original,
+ debounce: (fn: any) => fn,
+ };
+});
import { registerTestBed, TestBed } from '../../../../../../../../test_utils';
import { LoadMappingsProvider } from './load_mappings_provider';
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_clone.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_clone.test.tsx
index 81ee1435be5b..f8e0030441ba 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_clone.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_clone.test.tsx
@@ -11,18 +11,22 @@ import { PIPELINE_TO_CLONE, PipelinesCloneTestBed } from './helpers/pipelines_cl
const { setup } = pageHelpers.pipelinesClone;
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
// FLAKY: https://github.com/elastic/kibana/issues/66856
describe.skip(' ', () => {
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
index 4318062491df..2cfccbdc6d57 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
@@ -11,18 +11,22 @@ import { PipelinesCreateTestBed } from './helpers/pipelines_create.helpers';
const { setup } = pageHelpers.pipelinesCreate;
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
describe(' ', () => {
let testBed: PipelinesCreateTestBed;
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_edit.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_edit.test.tsx
index 477eec83f876..6c89216e3473 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_edit.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_edit.test.tsx
@@ -11,18 +11,22 @@ import { PIPELINE_TO_EDIT, PipelinesEditTestBed } from './helpers/pipelines_edit
const { setup } = pageHelpers.pipelinesEdit;
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(syntheticEvent.jsonString);
- }}
- />
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(syntheticEvent.jsonString);
+ }}
+ />
+ ),
+ };
+});
describe(' ', () => {
let testBed: PipelinesEditTestBed;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
index 97c74e0b2231..320ccd0cbe8c 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -8,37 +8,45 @@ import React from 'react';
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
import { PipelineProcessorsEditor, Props } from '../pipeline_processors_editor.container';
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
- // Mocking EuiCodeEditor, which uses React Ace under the hood
- EuiCodeEditor: (props: any) => (
- {
- props.onChange(e.jsonContent);
- }}
- />
- ),
-}));
-
-jest.mock('react-virtualized', () => ({
- ...jest.requireActual('react-virtualized'),
- AutoSizer: ({ children }: { children: any }) => (
- {children({ height: 500, width: 500 })}
- ),
-}));
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ // Mocking EuiCodeEditor, which uses React Ace under the hood
+ EuiCodeEditor: (props: any) => (
+ {
+ props.onChange(e.jsonContent);
+ }}
+ />
+ ),
+ };
+});
+
+jest.mock('react-virtualized', () => {
+ const original = jest.requireActual('react-virtualized');
+
+ return {
+ ...original,
+ AutoSizer: ({ children }: { children: any }) => (
+ {children({ height: 500, width: 500 })}
+ ),
+ };
+});
const testBedSetup = registerTestBed(PipelineProcessorsEditor, {
doMountAsync: false,
diff --git a/x-pack/plugins/license_management/__jest__/upload_license.test.tsx b/x-pack/plugins/license_management/__jest__/upload_license.test.tsx
index f76376b3fcef..947907c7dcc9 100644
--- a/x-pack/plugins/license_management/__jest__/upload_license.test.tsx
+++ b/x-pack/plugins/license_management/__jest__/upload_license.test.tsx
@@ -29,8 +29,6 @@ import {
// @ts-ignore
} from './api_responses';
-window.location.reload = () => {};
-
let store: any = null;
let component: any = null;
const history = scopedHistoryMock.create();
@@ -60,6 +58,14 @@ const thunkServices = {
};
describe('UploadLicense', () => {
+ beforeAll(() => {
+ Object.defineProperty(window, 'location', {
+ value: {
+ reload: jest.fn(),
+ },
+ });
+ });
+
beforeEach(() => {
store = licenseManagementStore({}, thunkServices);
component = (
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap
index 11138bf33704..9dc0e99669c7 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap
@@ -4,7 +4,7 @@ exports[`renderLegendDetailRow Should render as range 1`] = `
-
+
}
maxLabel="100_format"
minLabel="0_format"
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
index 4aaffef201b3..a9216e481776 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
@@ -11,7 +11,7 @@ exports[`LayerPanel is rendered 1`] = `
"get": [Function],
"remove": [Function],
"set": [Function],
- "store": undefined,
+ "store": Storage {},
},
}
}
diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap
index f3acc7074d27..6717e8fa5a51 100644
--- a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap
+++ b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap
@@ -95,7 +95,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription"
values={
Object {
- "learnMoreLink":
@@ -104,7 +104,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription.learnMoreLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -354,7 +354,7 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`]
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription"
values={
Object {
- "learnMoreLink":
@@ -363,7 +363,7 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`]
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription.learnMoreLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -599,7 +599,7 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription"
values={
Object {
- "learnMoreLink":
@@ -608,7 +608,7 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions
id="xpack.ml.ruleEditor.ruleEditorFlyout.rulesDescription.learnMoreLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/rule_action_panel.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/rule_action_panel.test.js.snap
index b512f6d7c014..d54f3c710b58 100644
--- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/rule_action_panel.test.js.snap
+++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/rule_action_panel.test.js.snap
@@ -47,7 +47,7 @@ exports[`RuleActionPanel renders panel for rule with a condition 1`] = `
/>,
},
Object {
- "description":
- ,
+ ,
"title": "",
},
Object {
@@ -101,7 +101,7 @@ exports[`RuleActionPanel renders panel for rule with a condition and scope, valu
/>,
},
Object {
- "description":
- ,
+ ,
"title": "",
},
Object {
@@ -143,7 +143,7 @@ exports[`RuleActionPanel renders panel for rule with scope, value in filter list
/>,
},
Object {
- "description":
- ,
+ ,
"title":
@@ -68,7 +68,7 @@ exports[`ValidateJob renders button and modal with a message 1`] = `
id="xpack.ml.validateJob.modal.linkToJobTipsText.mlJobTipsLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -149,7 +149,7 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = `
id="xpack.ml.validateJob.modal.linkToJobTipsText"
values={
Object {
- "mlJobTipsLink":
@@ -158,7 +158,7 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = `
id="xpack.ml.validateJob.modal.linkToJobTipsText.mlJobTipsLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap
index 59481cb08656..24c9dc85979e 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap
+++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap
@@ -83,12 +83,12 @@ exports[`Overrides render overrides 1`] = `
-
See more on accepted formats
-
+
}
label={
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.test.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.test.tsx
index 8a5dbc40db39..df450a33a52d 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.test.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.test.tsx
@@ -16,6 +16,17 @@ import { TimeBuckets as TimeBucketsClass } from '../util/time_buckets';
import { ChartTooltipService } from '../components/chart_tooltip';
import { OverallSwimlaneData } from './explorer_utils';
+jest.mock('d3', () => {
+ const original = jest.requireActual('d3');
+
+ return {
+ ...original,
+ transform: jest.fn().mockReturnValue({
+ translate: jest.fn().mockReturnValue(0),
+ }),
+ };
+});
+
jest.mock('./explorer_dashboard_service', () => ({
dragSelect$: {
subscribe: jest.fn(() => ({
diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap b/x-pack/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap
index 0b39841ed61e..c31ddc173f90 100644
--- a/x-pack/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap
+++ b/x-pack/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap
@@ -88,7 +88,7 @@ exports[`CalendarListsHeader renders header 1`] = `
values={
Object {
"br": ,
- "learnMoreLink":
@@ -97,7 +97,7 @@ exports[`CalendarListsHeader renders header 1`] = `
id="xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap b/x-pack/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap
index a8eebdaf6aa3..28389ef3c321 100644
--- a/x-pack/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap
@@ -12,11 +12,11 @@ exports[`Logs should render a link to filter by cluster uuid 1`] = `
id="xpack.monitoring.logs.listing.linkText"
values={
Object {
- "link":
Logs
- ,
+ ,
}
}
/>
@@ -36,11 +36,11 @@ exports[`Logs should render a link to filter by cluster uuid and index uuid 1`]
id="xpack.monitoring.logs.listing.linkText"
values={
Object {
- "link":
Logs
- ,
+ ,
}
}
/>
@@ -60,11 +60,11 @@ exports[`Logs should render a link to filter by cluster uuid and node uuid 1`] =
id="xpack.monitoring.logs.listing.linkText"
values={
Object {
- "link":
Logs
- ,
+ ,
}
}
/>
@@ -286,11 +286,11 @@ exports[`Logs should render normally 1`] = `
id="xpack.monitoring.logs.listing.linkText"
values={
Object {
- "link":
Logs
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/monitoring/public/components/logs/__snapshots__/reason.test.js.snap b/x-pack/plugins/monitoring/public/components/logs/__snapshots__/reason.test.js.snap
index de4b888a4854..b63fe7047e96 100644
--- a/x-pack/plugins/monitoring/public/components/logs/__snapshots__/reason.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/logs/__snapshots__/reason.test.js.snap
@@ -12,7 +12,7 @@ exports[`Logs should render a default message 1`] = `
id="xpack.monitoring.logs.reason.defaultMessage"
values={
Object {
- "link":
@@ -21,7 +21,7 @@ exports[`Logs should render a default message 1`] = `
id="xpack.monitoring.logs.reason.defaultMessageLink"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -41,12 +41,12 @@ exports[`Logs should render with a bad indices reason 1`] = `
id="xpack.monitoring.logs.reason.correctIndexNameMessage"
values={
Object {
- "link":
Click here for more information
- ,
+ ,
}
}
/>
@@ -66,12 +66,12 @@ exports[`Logs should render with a no cluster found reason 1`] = `
id="xpack.monitoring.logs.reason.noClusterMessage"
values={
Object {
- "link":
setup
- ,
+ ,
}
}
/>
@@ -91,12 +91,12 @@ exports[`Logs should render with a no index found reason 1`] = `
id="xpack.monitoring.logs.reason.noIndexMessage"
values={
Object {
- "link":
setup
- ,
+ ,
}
}
/>
@@ -116,12 +116,12 @@ exports[`Logs should render with a no index pattern found reason 1`] = `
id="xpack.monitoring.logs.reason.noIndexPatternMessage"
values={
Object {
- "link":
Filebeat
- ,
+ ,
}
}
/>
@@ -141,12 +141,12 @@ exports[`Logs should render with a no node found reason 1`] = `
id="xpack.monitoring.logs.reason.noNodeMessage"
values={
Object {
- "link":
setup
- ,
+ ,
}
}
/>
@@ -166,12 +166,12 @@ exports[`Logs should render with a no structured logs reason 1`] = `
id="xpack.monitoring.logs.reason.notUsingStructuredLogsMessage"
values={
Object {
- "link":
points to JSON logs
- ,
+ ,
"varPaths":
var.paths
,
@@ -194,12 +194,12 @@ exports[`Logs should render with a no type found reason 1`] = `
id="xpack.monitoring.logs.reason.noTypeMessage"
values={
Object {
- "link":
these directions
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap
index a4b2ea8c2502..c5507efb989d 100644
--- a/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap
@@ -155,7 +155,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
Object {
"children":
-
@@ -164,7 +164,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
id="xpack.monitoring.metricbeatMigration.apmInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as the APM server",
@@ -214,7 +214,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
Object {
"link":
-
@@ -223,7 +223,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -281,7 +281,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
Object {
"link":
-
@@ -290,7 +290,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -305,7 +305,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
Object {
"children":
-
@@ -314,7 +314,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
id="xpack.monitoring.metricbeatMigration.apmInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
@@ -494,7 +494,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
Object {
"children":
-
@@ -503,7 +503,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
id="xpack.monitoring.metricbeatMigration.beatsInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as this beat",
@@ -553,7 +553,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
values={
Object {
"beatType": "beat",
- "link":
@@ -566,7 +566,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
}
}
/>
- ,
+ ,
}
}
/>
@@ -590,7 +590,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
Object {
"link":
-
@@ -599,7 +599,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -657,7 +657,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
Object {
"link":
-
@@ -666,7 +666,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -681,7 +681,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
Object {
"children":
-
@@ -690,7 +690,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
id="xpack.monitoring.metricbeatMigration.beatsInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
@@ -859,7 +859,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
Object {
"children":
-
@@ -868,7 +868,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as Elasticsearch",
@@ -926,7 +926,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
Object {
"link":
-
@@ -935,7 +935,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -993,7 +993,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
Object {
"link":
-
@@ -1002,7 +1002,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1017,7 +1017,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
Object {
"children":
-
@@ -1026,7 +1026,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
@@ -1210,7 +1210,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
Object {
"children":
-
@@ -1219,7 +1219,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as Kibana",
@@ -1269,7 +1269,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
Object {
"link":
-
@@ -1278,7 +1278,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1336,7 +1336,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
Object {
"link":
-
@@ -1345,7 +1345,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1360,7 +1360,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
Object {
"children":
-
@@ -1369,7 +1369,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
@@ -1544,7 +1544,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
Object {
"children":
-
@@ -1553,7 +1553,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
id="xpack.monitoring.metricbeatMigration.logstashInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as Logstash",
@@ -1603,7 +1603,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
Object {
"link":
-
@@ -1612,7 +1612,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1670,7 +1670,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
Object {
"link":
-
@@ -1679,7 +1679,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1694,7 +1694,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
Object {
"children":
-
@@ -1703,7 +1703,7 @@ exports[`Flyout logstash part two should show instructions to migrate to metricb
id="xpack.monitoring.metricbeatMigration.logstashInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
@@ -1821,7 +1821,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
Object {
"children":
-
@@ -1830,7 +1830,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
id="xpack.monitoring.metricbeatMigration.beatsInstructions.installMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Install Metricbeat on the same server as this filebeat",
@@ -1880,7 +1880,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
values={
Object {
"beatType": "filebeat",
- "link":
@@ -1893,7 +1893,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
}
}
/>
- ,
+ ,
}
}
/>
@@ -1917,7 +1917,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
Object {
"link":
-
@@ -1926,7 +1926,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -1984,7 +1984,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
Object {
"link":
-
@@ -1993,7 +1993,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
id="xpack.monitoring.metricbeatMigration.securitySetupLinkText"
values={Object {}}
/>
-
+
,
}
}
@@ -2008,7 +2008,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
Object {
"children":
-
@@ -2017,7 +2017,7 @@ exports[`Flyout should render the beat type for beats for the enabling metricbea
id="xpack.monitoring.metricbeatMigration.beatsInstructions.startMetricbeatLinkText"
values={Object {}}
/>
-
+
,
"title": "Start Metricbeat",
diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts
index 716211cd4114..1ab51dda9546 100644
--- a/x-pack/plugins/monitoring/server/config.test.ts
+++ b/x-pack/plugins/monitoring/server/config.test.ts
@@ -4,10 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { createConfig, configSchema } from './config';
-jest.mock('fs', () => ({
- ...jest.requireActual('fs'),
- readFileSync: jest.fn().mockImplementation((path: string) => `contents-of-${path}`),
-}));
+jest.mock('fs', () => {
+ const original = jest.requireActual('fs');
+
+ return {
+ ...original,
+ readFileSync: jest.fn().mockImplementation((path: string) => `contents-of-${path}`),
+ };
+});
describe('config schema', () => {
it('generates proper defaults', () => {
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
index bcc9a3f14d48..e4c8159f5982 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
+++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
@@ -703,7 +703,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
id="xpack.remoteClusters.remoteClusterForm.fieldServerNameHelpText"
values={
Object {
- "learnMoreLink":
- ,
+ ,
}
}
/>
@@ -812,7 +812,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
id="xpack.remoteClusters.remoteClusterForm.fieldServerNameHelpText"
values={
Object {
- "learnMoreLink":
- ,
+ ,
}
}
>
@@ -976,7 +976,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription"
values={
Object {
- "learnMoreLink":
- ,
+ ,
"optionName":
- ,
+ ,
"optionName":
{
});
});
- afterAll(() => {
- delete (window as any).location;
- });
-
it('renders as expected when state is available', async () => {
const coreStartMock = coreMock.createStart();
coreStartMock.http.get.mockResolvedValue({ accessAgreement: 'This is [link](../link)' });
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx
index 9675be2a4d6a..39131f9f4499 100644
--- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx
@@ -47,10 +47,6 @@ describe('LoginForm', () => {
});
});
- afterAll(() => {
- delete (window as any).location;
- });
-
it('renders as expected', () => {
const coreStartMock = coreMock.createStart();
expect(
diff --git a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx
index ab107e46dfff..5370c981f2b6 100644
--- a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx
+++ b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx
@@ -48,10 +48,6 @@ describe('LoginPage', () => {
resetHttpMock();
});
- afterAll(() => {
- delete (window as any).location;
- });
-
describe('page', () => {
it('renders as expected', async () => {
const coreStartMock = coreMock.createStart();
diff --git a/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts b/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts
index c17a0c2ca27b..279500d14f21 100644
--- a/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts
+++ b/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts
@@ -21,11 +21,6 @@ describe('logoutApp', () => {
});
});
- afterAll(() => {
- delete (window as any).sessionStorage;
- delete (window as any).location;
- });
-
it('properly registers application', () => {
const coreSetupMock = coreMock.createSetup();
diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
index 6d1e0054078b..050af4bd20a4 100644
--- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
@@ -61,7 +61,7 @@ exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = `
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription"
values={
Object {
- "link":
@@ -70,7 +70,7 @@ exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = `
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText"
values={Object {}}
/>
- ,
+ ,
}
}
>
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
index 46fb9b857267..1c020685c246 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
@@ -13,7 +13,7 @@ exports[`it renders without crashing 1`] = `
id="xpack.security.management.editRole.elasticSearchPrivileges.manageRoleActionsDescription"
values={Object {}}
/>
-
-
+
}
title={
@@ -76,7 +76,7 @@ exports[`it renders without crashing 1`] = `
id="xpack.security.management.editRole.elasticSearchPrivileges.howToBeSubmittedOnBehalfOfOtherUsersDescription"
values={Object {}}
/>
-
-
+
}
title={
diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx
index ad69fa7ce0d3..c1c6a9f69b6e 100644
--- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx
+++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx
@@ -22,7 +22,7 @@ describe('SecurityNavControl', () => {
const wrapper = shallowWithIntl( );
const { button } = wrapper.find(EuiPopover).props();
expect(button).toMatchInlineSnapshot(`
- {
-
+
`);
});
@@ -49,7 +49,7 @@ describe('SecurityNavControl', () => {
wrapper.update();
const { button } = wrapper.find(EuiPopover).props();
expect(button).toMatchInlineSnapshot(`
- {
name="foo"
size="s"
/>
-
+
`);
});
diff --git a/x-pack/plugins/security/public/session/session_expired.test.ts b/x-pack/plugins/security/public/session/session_expired.test.ts
index 1ec2f772050f..de0a76c28d54 100644
--- a/x-pack/plugins/security/public/session/session_expired.test.ts
+++ b/x-pack/plugins/security/public/session/session_expired.test.ts
@@ -12,8 +12,6 @@ describe('#logout', () => {
const LOGOUT_URL = '/logout';
const TENANT = '/some-basepath';
- let newUrlPromise: Promise;
-
beforeAll(() => {
Object.defineProperty(window, 'sessionStorage', {
value: {
@@ -24,13 +22,16 @@ describe('#logout', () => {
});
beforeEach(() => {
- window.history.pushState({}, '', CURRENT_URL);
- mockGetItem.mockReset();
- newUrlPromise = new Promise((resolve) => {
- jest.spyOn(window.location, 'assign').mockImplementation((url) => {
- resolve(url);
- });
+ Object.defineProperty(window, 'location', {
+ value: {
+ assign: jest.fn(),
+ pathname: CURRENT_URL,
+ search: '',
+ hash: '',
+ },
+ configurable: true,
});
+ mockGetItem.mockReset();
});
afterAll(() => {
@@ -42,7 +43,9 @@ describe('#logout', () => {
sessionExpired.logout();
const next = `&next=${encodeURIComponent(CURRENT_URL)}`;
- await expect(newUrlPromise).resolves.toBe(`${LOGOUT_URL}?msg=SESSION_EXPIRED${next}`);
+ await expect(window.location.assign).toHaveBeenCalledWith(
+ `${LOGOUT_URL}?msg=SESSION_EXPIRED${next}`
+ );
});
it(`adds 'provider' parameter when sessionStorage contains the provider name for this tenant`, async () => {
@@ -57,7 +60,7 @@ describe('#logout', () => {
const next = `&next=${encodeURIComponent(CURRENT_URL)}`;
const provider = `&provider=${providerName}`;
- await expect(newUrlPromise).resolves.toBe(
+ await expect(window.location.assign).toBeCalledWith(
`${LOGOUT_URL}?msg=SESSION_EXPIRED${next}${provider}`
);
});
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
index 77c7efec262f..89f6399071dd 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
@@ -9,12 +9,16 @@ import { shallow } from 'enzyme';
import { PrePackagedRulesPrompt } from './load_empty_prompt';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../../common/components/link_to');
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap
index 1bee36ed9e18..549f0590681b 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap
@@ -10,7 +10,7 @@ exports[`RuleActionsOverflow snapshots renders correctly against snapshot 1`] =
delay="regular"
position="top"
>
- ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../../../common/components/link_to');
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
index cc39a26a8fac..b7a2d017c366 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.test.tsx
@@ -11,12 +11,16 @@ import { TestProviders } from '../../../../../common/mock';
import { CreateRulePage } from './index';
import { useUserInfo } from '../../../../components/user_info';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
index 1c947deb95f9..7e6cd48ddc00 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/index.test.tsx
@@ -11,12 +11,16 @@ import { RulesPage } from './index';
import { useUserInfo } from '../../../components/user_info';
import { usePrePackagedRules } from '../../../../alerts/containers/detection_engine/rules';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../../common/components/link_to');
jest.mock('../../../components/user_info');
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
index 67b0f5636746..f4fd7cc67224 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx
@@ -15,12 +15,16 @@ import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { useUpdateCases } from '../../containers/use_bulk_update_case';
import { EuiTableRow } from '@elastic/eui';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../common/components/link_to');
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
index 1721cb5f819f..86ec7d79a3f7 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/actions.test.tsx
@@ -15,12 +15,16 @@ import * as i18n from './translations';
jest.mock('../../containers/use_delete_cases');
const useDeleteCasesMock = useDeleteCases as jest.Mock;
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
describe('CaseView actions', () => {
const handleOnDeleteConfirm = jest.fn();
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
index 1315cf1c962e..5e19211b4707 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
@@ -12,12 +12,16 @@ import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button';
import { TestProviders } from '../../../common/mock';
import { searchURL } from './__mock__';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../common/components/link_to');
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
index 9dfeec3b26ba..f2de830a7164 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx
@@ -15,12 +15,16 @@ import { useGetActionLicense } from '../../containers/use_get_action_license';
import { getKibanaConfigError, getLicenseError } from './helpers';
import { connectorsMock } from '../../containers/configure/mock';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../common/components/link_to');
jest.mock('../../containers/use_get_action_license');
diff --git a/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx
index 023768c6af07..24141767079e 100644
--- a/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx
@@ -21,8 +21,10 @@ import {
jest.mock('../../lib/kibana');
jest.mock('@elastic/charts', () => {
+ const original = jest.requireActual('@elastic/charts');
+
return {
- ...jest.requireActual('@elastic/charts'),
+ ...original,
getSpecId: jest.fn(() => {}),
};
});
diff --git a/x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap
index f9ee342967b8..65893f84f5e5 100644
--- a/x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/empty_page/__snapshots__/index.test.tsx.snap
@@ -3,7 +3,7 @@
exports[`renders correctly 1`] = `
-
+
}
iconType="securityAnalyticsApp"
title={
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
index ef838fc50d10..30f510509913 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx
@@ -13,12 +13,16 @@ import { HeaderPage } from './index';
import { useMountAppended } from '../../utils/use_mount_appended';
import { SecurityPageName } from '../../../app/types';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../link_to');
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
index 249cc1fe5f89..a9ec474a7b68 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap
@@ -82,7 +82,7 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
}
@@ -105,14 +105,14 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
-
+
17
-
+
,
"title": "Max Anomaly Score",
},
Object {
- "description":
- ,
+ ,
"title":
,
},
Object {
- "description":
-
Narrow to this date range
-
+
- ,
+ ,
"title": "Detected",
},
Object {
- "description":
process.name: "du"
- ,
+ ,
"title": "Anomalous Entity",
},
Object {
- "description":
user.name: "root"
- ,
+ ,
"title": "Influenced By",
},
]
diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap
index 997ed632c798..ce878d379b5c 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap
@@ -9,7 +9,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
id="xpack.securitySolution.components.mlPopup.anomalyDetectionDescription"
values={
Object {
- "machineLearning":
@@ -18,7 +18,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
id="xpack.securitySolution.components.mlPopup.machineLearningLink"
values={Object {}}
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap
index 7e9e81328809..40b509ae683f 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap
@@ -15,7 +15,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
id="xpack.securitySolution.components.mlPopup.upgradeDescription"
values={
Object {
- "cloudLink":
@@ -24,7 +24,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
id="xpack.securitySolution.components.mlPopup.cloudLink"
values={Object {}}
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
index 69e6ef8688c4..a99497090f84 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
@@ -15,12 +15,16 @@ import { HostsTableType } from '../../../hosts/store/model';
import { RouteSpyState } from '../../utils/route/types';
import { SiemNavigationProps, SiemNavigationComponentProps } from './types';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('./breadcrumbs', () => ({
setBreadcrumbs: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
index 99d6f7840a7d..977c7808b6c8 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
@@ -19,12 +19,16 @@ import { TabNavigationProps } from './types';
jest.mock('../../../lib/kibana');
jest.mock('../../link_to');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- push: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ push: jest.fn(),
+ }),
+ };
+});
describe('Tab Navigation', () => {
const pageName = SecurityPageName.hosts;
diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx
index 99510a1b4b42..19321622d75f 100644
--- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx
@@ -267,44 +267,6 @@ describe('SIEM Super Date Picker', () => {
});
});
- describe('Pick Absolute Date', () => {
- let wrapper = mount(
-
-
-
- );
- beforeEach(() => {
- wrapper = mount(
-
-
-
- );
- wrapper.find('[data-test-subj="superDatePickerShowDatesButton"]').first().simulate('click');
- wrapper.update();
-
- wrapper
- .find('[data-test-subj="superDatePickerstartDatePopoverButton"]')
- .first()
- .simulate('click');
- wrapper.update();
-
- wrapper.find('[data-test-subj="superDatePickerAbsoluteTab"]').first().simulate('click');
- wrapper.update();
-
- wrapper.find('button.react-datepicker__navigation--previous').first().simulate('click');
- wrapper.update();
-
- wrapper.find('div.react-datepicker__day').at(1).simulate('click');
- wrapper.update();
-
- wrapper
- .find('button[data-test-subj="superDatePickerApplyTimeButton"]')
- .first()
- .simulate('click');
- wrapper.update();
- });
- });
-
describe('#makeMapStateToProps', () => {
test('it should return the same shallow references given the same input twice', () => {
const mapStateToProps = makeMapStateToProps();
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
index d545de9e7a6b..ae25e66b2af8 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
@@ -26,12 +26,16 @@ import {
timelineDefaults,
} from '../../../timelines/components/manage_timeline';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../link_to');
jest.mock('../../lib/kibana');
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
index 62960d4fae71..b1979c501c77 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
@@ -13,12 +13,16 @@ import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { allEvents, defaultOptions } from './helpers';
import { TopN } from './top_n';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- useHistory: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ useHistory: jest.fn(),
+ }),
+ };
+});
jest.mock('../../lib/kibana');
jest.mock('../link_to');
diff --git a/x-pack/plugins/security_solution/public/network/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap
index efc4d4be9e95..6e38b5eeff5f 100644
--- a/x-pack/plugins/security_solution/public/network/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/network/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap
@@ -7,7 +7,21 @@ exports[`FlowTargetSelect Component rendering it renders the FlowTargetSelect 1`
hasDividers={false}
isInvalid={false}
isLoading={false}
- onChange={[MockFunction]}
+ onChange={
+ [MockFunction] {
+ "calls": Array [
+ Array [
+ "destination",
+ ],
+ ],
+ "results": Array [
+ Object {
+ "type": "return",
+ "value": undefined,
+ },
+ ],
+ }
+ }
options={
Array [
Object {
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
index cefe2821b524..570e20dc5656 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx
@@ -95,13 +95,17 @@ const getSourceDestinationInstance = () => (
/>
);
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- createHref: jest.fn(),
- push: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ createHref: jest.fn(),
+ push: jest.fn(),
+ }),
+ };
+});
describe('SourceDestination', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap
index 8742e3f3a1c0..8d4de5b90fae 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap
@@ -10,7 +10,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
@@ -35,7 +35,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
max={157258653}
/>
-
+
}
buttonContentClassName="accordion-button"
id="host-stat-accordion-groupauditbeat"
@@ -243,7 +243,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
@@ -268,7 +268,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
max={157258653}
/>
-
+
}
buttonContentClassName="accordion-button"
id="host-stat-accordion-groupendgame"
@@ -508,7 +508,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
@@ -533,7 +533,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
max={157258653}
/>
-
+
}
buttonContentClassName="accordion-button"
id="host-stat-accordion-groupfilebeat"
@@ -581,7 +581,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
@@ -606,7 +606,7 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt
max={157258653}
/>
-
+
}
buttonContentClassName="accordion-button"
id="host-stat-accordion-groupwinlogbeat"
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap
index 4bc02aa54a45..f6060fc06095 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap
@@ -10,7 +10,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
@@ -35,7 +35,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
max={13748195}
/>
-
+
}
buttonContentClassName="accordion-button"
id="network-stat-accordion-groupauditbeat"
@@ -83,7 +83,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
@@ -108,7 +108,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
max={13748195}
/>
-
+
}
buttonContentClassName="accordion-button"
id="network-stat-accordion-groupfilebeat"
@@ -284,7 +284,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
@@ -309,7 +309,7 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet
max={13748195}
/>
-
+
}
buttonContentClassName="accordion-button"
id="network-stat-accordion-grouppacketbeat"
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
index 24dc806838d9..92ef5c41f3b4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
@@ -10,7 +10,7 @@ import { ActionCreator } from 'typescript-fsa';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { TestProviders } from '../../../common/mock';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../store/timeline/model';
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx
index ddb8fb023195..3ddc1efe9a47 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx
@@ -10,26 +10,34 @@ import React from 'react';
import { TestProviders } from '../../../../common/mock';
import { FlyoutHeaderWithCloseButton } from '.';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: jest.fn(),
-}));
-jest.mock('../../../../common/lib/kibana', () => ({
- ...jest.requireActual('../../../../common/lib/kibana'),
- useKibana: jest.fn().mockReturnValue({
- services: {
- application: {
- capabilities: {
- siem: {
- crud: true,
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: jest.fn(),
+ };
+});
+jest.mock('../../../../common/lib/kibana', () => {
+ const original = jest.requireActual('../../../../common/lib/kibana');
+
+ return {
+ ...original,
+ useKibana: jest.fn().mockReturnValue({
+ services: {
+ application: {
+ capabilities: {
+ siem: {
+ crud: true,
+ },
},
},
},
- },
- }),
- useUiSetting$: jest.fn().mockReturnValue([]),
- useGetUserSavedObjectPermissions: jest.fn(),
-}));
+ }),
+ useUiSetting$: jest.fn().mockReturnValue([]),
+ useGetUserSavedObjectPermissions: jest.fn(),
+ };
+});
describe('FlyoutHeaderWithCloseButton', () => {
test('renders correctly against snapshot', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap
index b4e6bce16305..9bf2b5c65e82 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap
@@ -5,7 +5,7 @@ exports[`NewNote renders correctly 1`] = `
data-test-subj="new-note-tabs"
initialSelectedTab={
Object {
- "content":
- ,
+ ,
"id": "preview",
"name": "Preview (Markdown)",
},
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx
index 3cec1fd34da9..b211847d06a2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { Direction } from '../../../../../../graphql/types';
import { TestProviders } from '../../../../../../common/mock';
-import { ColumnHeaderType } from '../../../../../../timelines/store/timeline/model';
+import { ColumnHeaderType } from '../../../../../store/timeline/model';
import { Sort } from '../../sort';
import { CloseButton } from '../actions';
import { defaultHeaders } from '../default_headers';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
index 9ced194aceb3..775c26e82d27 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx
@@ -205,7 +205,7 @@ describe('Body', () => {
.exists()
).toEqual(true);
});
- });
+ }, 20000);
});
describe('action on event', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
index 5f4f5a5da352..6c7a74d840d0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
@@ -13,13 +13,17 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { SystemGenericFileDetails, SystemGenericFileLine } from './generic_file_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- createHref: jest.fn(),
- push: jest.fn(),
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ createHref: jest.fn(),
+ push: jest.fn(),
+ }),
+ };
+});
describe('SystemGenericFileDetails', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
index b11f6dfdf9d8..5ccc8911d197 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx
@@ -40,10 +40,14 @@ mockUseResizeObserver.mockImplementation(() => ({}));
const mockUseSignalIndex: jest.Mock = useSignalIndex as jest.Mock;
jest.mock('../../../alerts/containers/detection_engine/alerts/use_signal_index');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: jest.fn(),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: jest.fn(),
+ };
+});
jest.mock('../flyout/header_with_close_button');
describe('StatefulTimeline', () => {
let props = {} as StatefulTimelineProps;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx
index 37e8507d0a1a..3078700a29d7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx
@@ -26,9 +26,10 @@ import { act } from 'react-dom/test-utils';
jest.mock('../../../../common/components/link_to');
jest.mock('../../../../common/lib/kibana', () => {
- const originalModule = jest.requireActual('../../../../common/lib/kibana');
+ const original = jest.requireActual('../../../../common/lib/kibana');
+
return {
- ...originalModule,
+ ...original,
useKibana: jest.fn().mockReturnValue({
services: {
application: {
@@ -54,27 +55,26 @@ jest.mock('../../../../common/components/utils', () => {
});
jest.mock('react-redux', () => {
- const originalModule = jest.requireActual('react-redux');
+ const original = jest.requireActual('react-redux');
return {
- ...originalModule,
- useSelector: jest.fn().mockReturnValue({ savedObjectId: '1' }),
+ ...original,
+ useDispatch: () => mockDispatch,
+ useSelector: jest.fn().mockReturnValue({ savedObjectId: '1', urlState: {} }),
};
});
-
-jest.mock('react-redux', () => ({
- ...jest.requireActual('react-redux'),
- useDispatch: () => mockDispatch,
- useSelector: jest.fn().mockReturnValue({ savedObjectId: '1', urlState: {} }),
-}));
const mockHistoryPush = jest.fn();
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useHistory: () => ({
- push: mockHistoryPush,
- }),
-}));
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+
+ return {
+ ...original,
+ useHistory: () => ({
+ push: mockHistoryPush,
+ }),
+ };
+});
jest.mock('./use_create_timeline', () => ({
useCreateTimelineButton: jest.fn().mockReturnValue({ getButton: jest.fn() }),
@@ -334,7 +334,7 @@ describe('Properties', () => {
expect(wrapper.find('[data-test-subj="avatar"]').exists()).toEqual(false);
});
- test('insert timeline - new case', () => {
+ test('insert timeline - new case', async () => {
const wrapper = mount(
diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
index 06f2e6cd83ed..cdf9ee5e678b 100644
--- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
+++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
@@ -28,8 +28,10 @@ jest.mock('ui/i18n', () => {
// Mocking FormattedDate and FormattedTime due to timezone differences on CI
jest.mock('@kbn/i18n/react', () => {
+ const original = jest.requireActual('@kbn/i18n/react');
+
return {
- ...jest.requireActual('@kbn/i18n/react'),
+ ...original,
FormattedDate: () => '',
FormattedTime: () => '',
};
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap
index 2aec970def9d..2fddf1d155a5 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap
@@ -34,7 +34,7 @@ exports[`renders without crashing 1`] = `
id="xpack.spaces.management.spaceIdentifier.urlIdentifierLabel"
values={Object {}}
/>
-
@@ -43,7 +43,7 @@ exports[`renders without crashing 1`] = `
id="xpack.spaces.management.spaceIdentifier.customizeSpaceLinkText"
values={Object {}}
/>
-
+
}
labelType="label"
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
index 25d735bd71dd..43ae75b74c88 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
@@ -67,7 +67,7 @@ exports[`EnabledFeatures renders as expected 1`] = `
id="xpack.spaces.management.enabledSpaceFeatures.goToRolesLink"
values={
Object {
- "rolesLink":
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap
index 10c3eed388ff..22d65f4600e0 100644
--- a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap
@@ -4,7 +4,7 @@ exports[`NavControlPopover renders without crashing 1`] = `
-
+
}
closePopover={[Function]}
data-test-subj="spacesNavSelector"
diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
index ba34db7b6296..db9ac1e93633 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
+++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
@@ -8,7 +8,6 @@ import React from 'react';
import { render, wait } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
-import '@testing-library/jest-dom/extend-expect';
import { CoreSetup } from 'src/core/public';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
index f2837d30402d..ffd6c30db45f 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { render } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import { StepCreateForm } from './step_create_form';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
index 60df5b14bb49..8c919a5185d7 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { render, wait } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import { I18nProvider } from '@kbn/i18n/react';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
index 19b2633b04ec..dc3d950938c9 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { render, wait } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import {
PivotAggsConfig,
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
index 3fbcd13e98f5..1d908920db8b 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
@@ -10,7 +10,6 @@ import { HealthCheck } from './health_check';
import { act } from 'react-dom/test-utils';
import { httpServiceMock } from '../../../../../../src/core/public/mocks';
-import '@testing-library/jest-dom/extend-expect';
const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' };
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx
index 9c017aa6fd31..2917be943d27 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx
@@ -13,10 +13,10 @@ import {
describe('checkActionTypeEnabled', () => {
test(`returns isEnabled:true when action type isn't provided`, async () => {
expect(checkActionTypeEnabled()).toMatchInlineSnapshot(`
- Object {
- "isEnabled": true,
- }
- `);
+ Object {
+ "isEnabled": true,
+ }
+ `);
});
test('returns isEnabled:true when action type is enabled', async () => {
@@ -29,10 +29,10 @@ describe('checkActionTypeEnabled', () => {
enabledInLicense: true,
};
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
- Object {
- "isEnabled": true,
- }
- `);
+ Object {
+ "isEnabled": true,
+ }
+ `);
});
test('returns isEnabled:false when action type is disabled by license', async () => {
@@ -45,28 +45,28 @@ describe('checkActionTypeEnabled', () => {
enabledInLicense: false,
};
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
- Object {
- "isEnabled": false,
- "message": "This connector requires a Basic license.",
- "messageCard":
-
-
-
- ,
- }
- `);
+
+
+
+ ,
+ }
+ `);
});
test('returns isEnabled:false when action type is disabled by config', async () => {
@@ -79,16 +79,16 @@ describe('checkActionTypeEnabled', () => {
enabledInLicense: true,
};
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
- Object {
- "isEnabled": false,
- "message": "This connector is disabled by the Kibana configuration.",
- "messageCard": ,
- }
- `);
+ Object {
+ "isEnabled": false,
+ "message": "This connector is disabled by the Kibana configuration.",
+ "messageCard": ,
+ }
+ `);
});
});
@@ -126,10 +126,10 @@ describe('checkActionFormActionTypeEnabled', () => {
expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors))
.toMatchInlineSnapshot(`
- Object {
- "isEnabled": true,
- }
- `);
+ Object {
+ "isEnabled": true,
+ }
+ `);
});
test('returns isEnabled:false when action type is disabled by config and not preconfigured', async () => {
@@ -143,15 +143,15 @@ describe('checkActionFormActionTypeEnabled', () => {
};
expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors))
.toMatchInlineSnapshot(`
- Object {
- "isEnabled": false,
- "message": "This connector is disabled by the Kibana configuration.",
- "messageCard": ,
- }
- `);
+ Object {
+ "isEnabled": false,
+ "message": "This connector is disabled by the Kibana configuration.",
+ "messageCard": ,
+ }
+ `);
});
});
diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx
index f43d832b1eda..745b3c403afc 100644
--- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx
+++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { cleanup, fireEvent, render } from '@testing-library/react/pure';
-import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global
import { TEST_SUBJ_ACTION_FACTORY_ITEM, TEST_SUBJ_SELECTED_ACTION_FACTORY } from './action_wizard';
import { dashboardFactory, dashboards, Demo, urlFactory } from './test_data';
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
index 161caa9782f0..e98701a05ce8 100644
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
+++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { cleanup, fireEvent, render, wait } from '@testing-library/react/pure';
-import '@testing-library/jest-dom/extend-expect';
import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns';
import { dashboardFactory, urlFactory } from '../../../components/action_wizard/test_data';
import { StubBrowserStorage } from '../../../../../../../src/test_utils/public/stub_browser_storage';
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx
index 4a4d67b08b1d..715bba74cf8e 100644
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx
+++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { cleanup, fireEvent, render } from '@testing-library/react/pure';
-import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global
import {
DrilldownListItem,
ListManageDrilldowns,
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap
index ae5f2d27ed7d..e51e3607613c 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap
@@ -39,7 +39,7 @@ exports[`CheckupTab render with deprecations 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail"
values={
Object {
- "snapshotRestoreDocsButton":
@@ -48,7 +48,7 @@ exports[`CheckupTab render with deprecations 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -320,7 +320,7 @@ exports[`CheckupTab render with error 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail"
values={
Object {
- "snapshotRestoreDocsButton":
@@ -329,7 +329,7 @@ exports[`CheckupTab render with error 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -385,7 +385,7 @@ exports[`CheckupTab render without deprecations 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail"
values={
Object {
- "snapshotRestoreDocsButton":
@@ -394,7 +394,7 @@ exports[`CheckupTab render without deprecations 1`] = `
id="xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel"
values={Object {}}
/>
- ,
+ ,
}
}
/>
@@ -427,7 +427,7 @@ exports[`CheckupTab render without deprecations 1`] = `
id="xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail"
values={
Object {
- "overviewTabButton":
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts
index 2a9449485f42..713d0cb85e2e 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts
@@ -326,7 +326,6 @@ describe('ReindexActions', () => {
});
it('fails after 10 attempts to lock', async () => {
- jest.setTimeout(20000); // increase the timeout
client.get.mockResolvedValue({
type: REINDEX_OP_TYPE,
id: consumerType,
@@ -339,10 +338,7 @@ describe('ReindexActions', () => {
actions.runWhileIndexGroupLocked(consumerType, async (m) => m)
).rejects.toThrow('Could not acquire lock for ML jobs');
expect(client.update).toHaveBeenCalledTimes(10);
-
- // Restore default timeout.
- jest.setTimeout(5000);
- });
+ }, 20000);
});
});
});
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
index 3a056e6621f5..7a61eb7391a1 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
@@ -31,11 +31,11 @@ exports[`ML Flyout component renders without errors 1`] = `
id="xpack.uptime.ml.enableAnomalyDetectionPanel.manageMLJobDescription"
values={
Object {
- "mlJobsPageLink":
Machine Learning jobs management page
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap
index cfdbb5184d17..22dab492a94a 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap
@@ -7,7 +7,7 @@ exports[`PingListExpandedRow renders expected elements for valid props 1`] = `
id="xpack.uptime.pingList.expandedRow.response_body.notRecorded"
values={
Object {
- "docsLink":
@@ -17,7 +17,7 @@ exports[`PingListExpandedRow renders expected elements for valid props 1`] = `
size="s"
type="popout"
/>
- ,
+ ,
}
}
/>
diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap
index 154ab6399452..20fc6d69c860 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap
@@ -126,9 +126,9 @@ exports[`PingList component renders sorted list without errors 1`] = `
Object {
"align": "right",
"field": "http.response.status_code",
- "name":
+ "name":
Response code
- ,
+ ,
"render": [Function],
},
Object {
diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap
index d7651fe6db43..0429d36bf874 100644
--- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap
@@ -19,7 +19,7 @@ exports[`DataOrIndexMissing component renders headingMessage 1`] = `
+
-
+
}
body={
diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
index ccf6ade66408..cdefa7db5688 100644
--- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
@@ -196,7 +196,7 @@ exports[`EmptyState component does not render empty state with appropriate base
>
+
-
+
}
body={
@@ -633,7 +633,7 @@ exports[`EmptyState component doesn't render child components when count is fals
>
+
-
+
}
body={
@@ -1075,7 +1075,7 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
>
+
-
+
}
body={
diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/filter_popover.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/filter_popover.test.tsx
index 4726a2f4a4ab..a64c7ce698cf 100644
--- a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/filter_popover.test.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/filter_popover.test.tsx
@@ -13,7 +13,7 @@ import { EuiFilterSelectItem } from '@elastic/eui';
describe('FilterPopover component', () => {
let props: FilterPopoverProps;
let setState: jest.Mock;
- let useStateSpy: jest.SpyInstance<[unknown, React.Dispatch], unknown[]>;
+ let useStateSpy: jest.SpyInstance;
beforeEach(() => {
props = {
diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
index 89cd5207fe25..3513606dbfe3 100644
--- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
+++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
@@ -49,32 +49,40 @@ const WATCH_VISUALIZE_DATA = {
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
-jest.mock('../../public/application/lib/api', () => ({
- ...jest.requireActual('../../public/application/lib/api'),
- loadIndexPatterns: async () => {
- const INDEX_PATTERNS = [
- { attributes: { title: 'index1' } },
- { attributes: { title: 'index2' } },
- { attributes: { title: 'index3' } },
- ];
- return await INDEX_PATTERNS;
- },
- getHttpClient: () => mockHttpClient,
-}));
-
-jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
- // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
- // which does not produce a valid component wrapper
- EuiComboBox: (props: any) => (
- {
- props.onChange([syntheticEvent['0']]);
- }}
- />
- ),
-}));
+jest.mock('../../public/application/lib/api', () => {
+ const original = jest.requireActual('../../public/application/lib/api');
+
+ return {
+ ...original,
+ loadIndexPatterns: async () => {
+ const INDEX_PATTERNS = [
+ { attributes: { title: 'index1' } },
+ { attributes: { title: 'index2' } },
+ { attributes: { title: 'index3' } },
+ ];
+ return await INDEX_PATTERNS;
+ },
+ getHttpClient: () => mockHttpClient,
+ };
+});
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
+ // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
+ // which does not produce a valid component wrapper
+ EuiComboBox: (props: any) => (
+ {
+ props.onChange([syntheticEvent['0']]);
+ }}
+ />
+ ),
+ };
+});
const { setup } = pageHelpers.watchCreateThreshold;
diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts
index 18acf7339580..c35bbcb171a3 100644
--- a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts
+++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts
@@ -16,18 +16,22 @@ import { getRandomString } from '../../../../test_utils';
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
-jest.mock('../../public/application/lib/api', () => ({
- ...jest.requireActual('../../public/application/lib/api'),
- loadIndexPatterns: async () => {
- const INDEX_PATTERNS = [
- { attributes: { title: 'index1' } },
- { attributes: { title: 'index2' } },
- { attributes: { title: 'index3' } },
- ];
- return await INDEX_PATTERNS;
- },
- getHttpClient: () => mockHttpClient,
-}));
+jest.mock('../../public/application/lib/api', () => {
+ const original = jest.requireActual('../../public/application/lib/api');
+
+ return {
+ ...original,
+ loadIndexPatterns: async () => {
+ const INDEX_PATTERNS = [
+ { attributes: { title: 'index1' } },
+ { attributes: { title: 'index2' } },
+ { attributes: { title: 'index3' } },
+ ];
+ return await INDEX_PATTERNS;
+ },
+ getHttpClient: () => mockHttpClient,
+ };
+});
const { setup } = pageHelpers.watchEdit;
diff --git a/x-pack/test_utils/jest/config.js b/x-pack/test_utils/jest/config.js
index f36099bd1f34..adee510ef284 100644
--- a/x-pack/test_utils/jest/config.js
+++ b/x-pack/test_utils/jest/config.js
@@ -31,12 +31,14 @@ export default {
coverageReporters: ['html'],
moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'node'],
modulePathIgnorePatterns: ['__fixtures__/', 'target/'],
+ testEnvironment: 'jest-environment-jsdom-thirteen',
testMatch: ['**/*.test.{js,ts,tsx}'],
testPathIgnorePatterns: [
'/packages/kbn-ui-framework/(dist|doc_site|generator-kui)/',
'/packages/kbn-pm/dist/',
`${RESERVED_DIR_JEST_INTEGRATION_TESTS}/`,
],
+ testRunner: 'jest-circus/runner',
transform: {
'^.+\\.(js|tsx?)$': '/../src/dev/jest/babel_transform.js',
'^.+\\.txt?$': 'jest-raw-loader',
diff --git a/x-pack/test_utils/testbed/types.ts b/x-pack/test_utils/testbed/types.ts
index 4f7b3d5bda2b..4975e073eea1 100644
--- a/x-pack/test_utils/testbed/types.ts
+++ b/x-pack/test_utils/testbed/types.ts
@@ -83,20 +83,24 @@ export interface TestBed {
/**
* Set the value of a or a mocked
* For the you need to mock it like this
- *
+ *
```typescript
- jest.mock('@elastic/eui', () => ({
- ...jest.requireActual('@elastic/eui'),
+ jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+
+ return {
+ ...original,
EuiSuperSelect: (props: any) => (
{
+ onChange={(e) => {
props.onChange(e.target.value);
}}
/>
),
- }));
+ };
+ });
```
* @param select The form select. Can either be a data-test-subj or a reactWrapper (can be a nested path. e.g. "myForm.myInput").
* @param value The value to set
diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json
index e0bf3b8f1694..306294c57b3c 100644
--- a/x-pack/tsconfig.json
+++ b/x-pack/tsconfig.json
@@ -39,7 +39,9 @@
"types": [
"node",
"jest",
- "flot"
+ "flot",
+ "jest-styled-components",
+ "@testing-library/jest-dom"
]
}
}
diff --git a/x-pack/typings/jest.d.ts b/x-pack/typings/jest.d.ts
index 5d2aa51284e5..488df5ad9237 100644
--- a/x-pack/typings/jest.d.ts
+++ b/x-pack/typings/jest.d.ts
@@ -12,23 +12,3 @@ type DeeplyMockedKeys = {
: DeeplyMockedKeys;
} &
T;
-
-// https://github.com/styled-components/jest-styled-components/issues/264
-declare namespace jest {
- interface AsymmetricMatcher {
- $$typeof: Symbol; //eslint-disable-line
- sample?: string | RegExp | object | Array | Function; // eslint-disable-line
- }
-
- type Value = string | number | RegExp | AsymmetricMatcher | undefined;
-
- interface Options {
- media?: string;
- modifier?: string;
- supports?: string;
- }
-
- interface Matchers {
- toHaveStyleRule(property: string, value?: Value, options?: Options): R;
- }
-}
diff --git a/yarn.lock b/yarn.lock
index e9b057799337..d3dd4aeabfdb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -39,6 +39,13 @@
dependencies:
"@babel/highlight" "^7.10.1"
+"@babel/code-frame@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a"
+ integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==
+ dependencies:
+ "@babel/highlight" "^7.10.3"
+
"@babel/compat-data@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db"
@@ -121,6 +128,28 @@
semver "^5.4.1"
source-map "^0.5.0"
+"@babel/core@^7.7.5":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e"
+ integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w==
+ dependencies:
+ "@babel/code-frame" "^7.10.3"
+ "@babel/generator" "^7.10.3"
+ "@babel/helper-module-transforms" "^7.10.1"
+ "@babel/helpers" "^7.10.1"
+ "@babel/parser" "^7.10.3"
+ "@babel/template" "^7.10.3"
+ "@babel/traverse" "^7.10.3"
+ "@babel/types" "^7.10.3"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.13"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
"@babel/generator@^7.0.0", "@babel/generator@^7.5.5", "@babel/generator@^7.9.0":
version "7.9.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce"
@@ -141,6 +170,16 @@
lodash "^4.17.13"
source-map "^0.5.0"
+"@babel/generator@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5"
+ integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA==
+ dependencies:
+ "@babel/types" "^7.10.3"
+ jsesc "^2.5.1"
+ lodash "^4.17.13"
+ source-map "^0.5.0"
+
"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee"
@@ -312,6 +351,15 @@
"@babel/template" "^7.10.1"
"@babel/types" "^7.10.1"
+"@babel/helper-function-name@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197"
+ integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.3"
+ "@babel/template" "^7.10.3"
+ "@babel/types" "^7.10.3"
+
"@babel/helper-function-name@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
@@ -328,6 +376,13 @@
dependencies:
"@babel/types" "^7.10.1"
+"@babel/helper-get-function-arity@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e"
+ integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg==
+ dependencies:
+ "@babel/types" "^7.10.3"
+
"@babel/helper-get-function-arity@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
@@ -518,6 +573,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
+"@babel/helper-validator-identifier@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15"
+ integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw==
+
"@babel/helper-validator-identifier@^7.9.0":
version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed"
@@ -584,6 +644,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d"
+ integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.3"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.0", "@babel/parser@^7.5.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
version "7.9.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
@@ -594,6 +663,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0"
integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==
+"@babel/parser@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315"
+ integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==
+
"@babel/plugin-proposal-async-generator-functions@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55"
@@ -798,13 +872,20 @@
"@babel/helper-create-regexp-features-plugin" "^7.8.8"
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-async-generators@^7.2.0", "@babel/plugin-syntax-async-generators@^7.8.0":
+"@babel/plugin-syntax-async-generators@^7.2.0", "@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-bigint@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea"
+ integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
"@babel/plugin-syntax-class-properties@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5"
@@ -812,6 +893,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.1"
+"@babel/plugin-syntax-class-properties@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz#6cb933a8872c8d359bfde69bbeaae5162fd1e8f7"
+ integrity sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
"@babel/plugin-syntax-decorators@^7.2.0":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz#8d2c15a9f1af624b0025f961682a9d53d3001bda"
@@ -847,7 +935,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-json-strings@^7.2.0", "@babel/plugin-syntax-json-strings@^7.8.0":
+"@babel/plugin-syntax-json-strings@^7.2.0", "@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
@@ -868,7 +956,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+"@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz#3995d7d7ffff432f6ddc742b47e730c054599897"
+ integrity sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
@@ -889,21 +984,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0":
+"@babel/plugin-syntax-object-rest-spread@^7.2.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-optional-catch-binding@^7.2.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+"@babel/plugin-syntax-optional-catch-binding@^7.2.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-optional-chaining@^7.8.0":
+"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
@@ -1889,7 +1984,7 @@
dependencies:
regenerator-runtime "^0.13.2"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.9.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
@@ -1903,7 +1998,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/template@^7.0.0", "@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
+"@babel/template@^7.0.0", "@babel/template@^7.3.3", "@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==
@@ -1921,6 +2016,15 @@
"@babel/parser" "^7.10.1"
"@babel/types" "^7.10.1"
+"@babel/template@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8"
+ integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA==
+ dependencies:
+ "@babel/code-frame" "^7.10.3"
+ "@babel/parser" "^7.10.3"
+ "@babel/types" "^7.10.3"
+
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.4.5", "@babel/traverse@^7.5.5", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892"
@@ -1951,6 +2055,21 @@
globals "^11.1.0"
lodash "^4.17.13"
+"@babel/traverse@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e"
+ integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug==
+ dependencies:
+ "@babel/code-frame" "^7.10.3"
+ "@babel/generator" "^7.10.3"
+ "@babel/helper-function-name" "^7.10.3"
+ "@babel/helper-split-export-declaration" "^7.10.1"
+ "@babel/parser" "^7.10.3"
+ "@babel/types" "^7.10.3"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.13"
+
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5"
@@ -1969,6 +2088,24 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@babel/types@^7.10.3":
+ version "7.10.3"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e"
+ integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.3"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.3.3":
+ version "7.9.6"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7"
+ integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.9.5"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
"@babel/types@^7.4":
version "7.9.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
@@ -1978,6 +2115,11 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@bcoe/v8-coverage@^0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
+ integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+
"@cnakazawa/watch@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@@ -2418,14 +2560,20 @@
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
-"@jest/console@^24.7.1":
- version "24.7.1"
- resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545"
- integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==
+"@istanbuljs/load-nyc-config@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
+ integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==
dependencies:
- "@jest/source-map" "^24.3.0"
- chalk "^2.0.1"
- slash "^2.0.0"
+ camelcase "^5.3.1"
+ find-up "^4.1.0"
+ js-yaml "^3.13.1"
+ resolve-from "^5.0.0"
+
+"@istanbuljs/schema@^0.1.2":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
+ integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@jest/console@^24.9.0":
version "24.9.0"
@@ -2436,49 +2584,59 @@
chalk "^2.0.1"
slash "^2.0.0"
-"@jest/core@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4"
- integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==
+"@jest/console@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.5.0.tgz#770800799d510f37329c508a9edd0b7b447d9abb"
+ integrity sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==
dependencies:
- "@jest/console" "^24.7.1"
- "@jest/reporters" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- ansi-escapes "^3.0.0"
- chalk "^2.0.1"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ jest-message-util "^25.5.0"
+ jest-util "^25.5.0"
+ slash "^3.0.0"
+
+"@jest/core@^25.5.4":
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.5.4.tgz#3ef7412f7339210f003cdf36646bbca786efe7b4"
+ integrity sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==
+ dependencies:
+ "@jest/console" "^25.5.0"
+ "@jest/reporters" "^25.5.1"
+ "@jest/test-result" "^25.5.0"
+ "@jest/transform" "^25.5.1"
+ "@jest/types" "^25.5.0"
+ ansi-escapes "^4.2.1"
+ chalk "^3.0.0"
exit "^0.1.2"
- graceful-fs "^4.1.15"
- jest-changed-files "^24.9.0"
- jest-config "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-message-util "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-resolve-dependencies "^24.9.0"
- jest-runner "^24.9.0"
- jest-runtime "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- jest-watcher "^24.9.0"
- micromatch "^3.1.10"
- p-each-series "^1.0.0"
- realpath-native "^1.1.0"
- rimraf "^2.5.4"
- slash "^2.0.0"
- strip-ansi "^5.0.0"
+ graceful-fs "^4.2.4"
+ jest-changed-files "^25.5.0"
+ jest-config "^25.5.4"
+ jest-haste-map "^25.5.1"
+ jest-message-util "^25.5.0"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.5.1"
+ jest-resolve-dependencies "^25.5.4"
+ jest-runner "^25.5.4"
+ jest-runtime "^25.5.4"
+ jest-snapshot "^25.5.1"
+ jest-util "^25.5.0"
+ jest-validate "^25.5.0"
+ jest-watcher "^25.5.0"
+ micromatch "^4.0.2"
+ p-each-series "^2.1.0"
+ realpath-native "^2.0.0"
+ rimraf "^3.0.0"
+ slash "^3.0.0"
+ strip-ansi "^6.0.0"
-"@jest/environment@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18"
- integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==
+"@jest/environment@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.5.0.tgz#aa33b0c21a716c65686638e7ef816c0e3a0c7b37"
+ integrity sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==
dependencies:
- "@jest/fake-timers" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
+ "@jest/fake-timers" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ jest-mock "^25.5.0"
"@jest/fake-timers@^24.9.0":
version "24.9.0"
@@ -2489,41 +2647,57 @@
jest-message-util "^24.9.0"
jest-mock "^24.9.0"
-"@jest/reporters@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43"
- integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==
- dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
+"@jest/fake-timers@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
+ integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-mock "^25.5.0"
+ jest-util "^25.5.0"
+ lolex "^5.0.0"
+
+"@jest/globals@^25.5.2":
+ version "25.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-25.5.2.tgz#5e45e9de8d228716af3257eeb3991cc2e162ca88"
+ integrity sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==
+ dependencies:
+ "@jest/environment" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ expect "^25.5.0"
+
+"@jest/reporters@^25.5.1":
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.5.1.tgz#cb686bcc680f664c2dbaf7ed873e93aa6811538b"
+ integrity sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==
+ dependencies:
+ "@bcoe/v8-coverage" "^0.2.3"
+ "@jest/console" "^25.5.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/transform" "^25.5.1"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ collect-v8-coverage "^1.0.0"
exit "^0.1.2"
glob "^7.1.2"
- istanbul-lib-coverage "^2.0.2"
- istanbul-lib-instrument "^3.0.1"
- istanbul-lib-report "^2.0.4"
- istanbul-lib-source-maps "^3.0.1"
- istanbul-reports "^2.2.6"
- jest-haste-map "^24.9.0"
- jest-resolve "^24.9.0"
- jest-runtime "^24.9.0"
- jest-util "^24.9.0"
- jest-worker "^24.6.0"
- node-notifier "^5.4.2"
- slash "^2.0.0"
- source-map "^0.6.0"
- string-length "^2.0.0"
-
-"@jest/source-map@^24.3.0":
- version "24.3.0"
- resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28"
- integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==
- dependencies:
- callsites "^3.0.0"
- graceful-fs "^4.1.15"
+ graceful-fs "^4.2.4"
+ istanbul-lib-coverage "^3.0.0"
+ istanbul-lib-instrument "^4.0.0"
+ istanbul-lib-report "^3.0.0"
+ istanbul-lib-source-maps "^4.0.0"
+ istanbul-reports "^3.0.2"
+ jest-haste-map "^25.5.1"
+ jest-resolve "^25.5.1"
+ jest-util "^25.5.0"
+ jest-worker "^25.5.0"
+ slash "^3.0.0"
source-map "^0.6.0"
+ string-length "^3.1.0"
+ terminal-link "^2.0.0"
+ v8-to-istanbul "^4.1.3"
+ optionalDependencies:
+ node-notifier "^6.0.0"
"@jest/source-map@^24.9.0":
version "24.9.0"
@@ -2534,6 +2708,15 @@
graceful-fs "^4.1.15"
source-map "^0.6.0"
+"@jest/source-map@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.5.0.tgz#df5c20d6050aa292c2c6d3f0d2c7606af315bd1b"
+ integrity sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==
+ dependencies:
+ callsites "^3.0.0"
+ graceful-fs "^4.2.4"
+ source-map "^0.6.0"
+
"@jest/test-result@^24.9.0":
version "24.9.0"
resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca"
@@ -2543,15 +2726,26 @@
"@jest/types" "^24.9.0"
"@types/istanbul-lib-coverage" "^2.0.0"
-"@jest/test-sequencer@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31"
- integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==
+"@jest/test-result@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.5.0.tgz#139a043230cdeffe9ba2d8341b27f2efc77ce87c"
+ integrity sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==
dependencies:
- "@jest/test-result" "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-runner "^24.9.0"
- jest-runtime "^24.9.0"
+ "@jest/console" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ collect-v8-coverage "^1.0.0"
+
+"@jest/test-sequencer@^25.5.4":
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz#9b4e685b36954c38d0f052e596d28161bdc8b737"
+ integrity sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==
+ dependencies:
+ "@jest/test-result" "^25.5.0"
+ graceful-fs "^4.2.4"
+ jest-haste-map "^25.5.1"
+ jest-runner "^25.5.4"
+ jest-runtime "^25.5.4"
"@jest/transform@^24.9.0":
version "24.9.0"
@@ -2575,14 +2769,27 @@
source-map "^0.6.1"
write-file-atomic "2.4.1"
-"@jest/types@^24.8.0":
- version "24.8.0"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
- integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
+"@jest/transform@^25.5.1":
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.5.1.tgz#0469ddc17699dd2bf985db55fa0fb9309f5c2db3"
+ integrity sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==
dependencies:
- "@types/istanbul-lib-coverage" "^2.0.0"
- "@types/istanbul-reports" "^1.1.1"
- "@types/yargs" "^12.0.9"
+ "@babel/core" "^7.1.0"
+ "@jest/types" "^25.5.0"
+ babel-plugin-istanbul "^6.0.0"
+ chalk "^3.0.0"
+ convert-source-map "^1.4.0"
+ fast-json-stable-stringify "^2.0.0"
+ graceful-fs "^4.2.4"
+ jest-haste-map "^25.5.1"
+ jest-regex-util "^25.2.6"
+ jest-util "^25.5.0"
+ micromatch "^4.0.2"
+ pirates "^4.0.1"
+ realpath-native "^2.0.0"
+ slash "^3.0.0"
+ source-map "^0.6.1"
+ write-file-atomic "^3.0.0"
"@jest/types@^24.9.0":
version "24.9.0"
@@ -2593,10 +2800,10 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
-"@jest/types@^25.1.0":
- version "25.1.0"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395"
- integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==
+"@jest/types@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
+ integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
@@ -3388,17 +3595,31 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f"
integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg==
-"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0":
+"@sinonjs/commons@^1":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393"
integrity sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==
dependencies:
type-detect "4.0.8"
+"@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0":
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
+ integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==
+ dependencies:
+ type-detect "4.0.8"
+
+"@sinonjs/commons@^1.7.0":
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1"
+ integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==
+ dependencies:
+ type-detect "4.0.8"
+
"@sinonjs/formatio@^3.2.1":
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.1.tgz#52310f2f9bcbc67bdac18c94ad4901b95fde267e"
- integrity sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c"
+ integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^3.1.0"
@@ -4318,19 +4539,19 @@
pretty-format "^24.9.0"
wait-for-expect "^3.0.0"
-"@testing-library/jest-dom@4.2.0":
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.0.tgz#32f8df3a78511b347d39374ea89dc8e0a1c2fb69"
- integrity sha512-H61OmRhGPWLrj9emyISx0qjp8jvC9RWyRniuLAq75Ny5XfPiOvWfnY3Wm2Tf0HXusX+PG40I94Gw792IAtSKKg==
+"@testing-library/jest-dom@^5.8.0":
+ version "5.8.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.8.0.tgz#815e830129c4dda6c8e9a725046397acec523669"
+ integrity sha512-9Y4FxYIxfwHpUyJVqI8EOfDP2LlEBqKwXE3F+V8ightji0M2rzQB+9kqZ5UJxNs+9oXJIgvYj7T3QaXLNHVDMw==
dependencies:
- "@babel/runtime" "^7.5.1"
- chalk "^2.4.1"
- css "^2.2.3"
+ "@babel/runtime" "^7.9.2"
+ "@types/testing-library__jest-dom" "^5.0.2"
+ chalk "^3.0.0"
+ css "^2.2.4"
css.escape "^1.5.1"
- jest-diff "^24.0.0"
- jest-matcher-utils "^24.0.0"
- lodash "^4.17.11"
- pretty-format "^24.0.0"
+ jest-diff "^25.1.0"
+ jest-matcher-utils "^25.1.0"
+ lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react-hooks@^3.2.1":
@@ -4464,10 +4685,10 @@
resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.4.tgz#bfd5b0d0d1ba13e351dff65b6e52783b816826c8"
integrity sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==
-"@types/babel__core@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51"
- integrity sha512-wJTeJRt7BToFx3USrCDs2BhEi4ijBInTQjOIukj6a/5tEkwpFMVZ+1ppgmE+Q/FQyc5P/VWUbx7I9NELrKruHA==
+"@types/babel__core@^7.1.2":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
+ integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
@@ -4475,10 +4696,10 @@
"@types/babel__template" "*"
"@types/babel__traverse" "*"
-"@types/babel__core@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
- integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==
+"@types/babel__core@^7.1.7":
+ version "7.1.7"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
+ integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
@@ -4886,7 +5107,7 @@
"@types/glob" "*"
fast-glob "^2.0.2"
-"@types/graceful-fs@*":
+"@types/graceful-fs@*", "@types/graceful-fs@^4.1.2":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f"
integrity sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==
@@ -5034,7 +5255,7 @@
resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d"
integrity sha512-zC/2EmD8scdsGIeE+Xg7kP7oi9VP90zgMQtm9Cr25av4V+a+k8slQyiT60qSw8KORYrOKlPXfHwoa1bQbRzskQ==
-"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
@@ -5054,19 +5275,13 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
-"@types/jest-diff@*":
- version "24.3.0"
- resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-24.3.0.tgz#29e237a3d954babfe6e23cc59b57ecd8ca8d858d"
- integrity sha512-vx1CRDeDUwQ0Pc7v+hS61O1ETA81kD04IMEC0hS1kPyVtHDdZrokAvpF7MT9VI/fVSzicelUZNCepDvhRV1PeA==
+"@types/jest@*", "@types/jest@^25.2.3":
+ version "25.2.3"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf"
+ integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==
dependencies:
- jest-diff "*"
-
-"@types/jest@24.0.19":
- version "24.0.19"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.19.tgz#f7036058d2a5844fe922609187c0ad8be430aff5"
- integrity sha512-YYiqfSjocv7lk5H/T+v5MjATYjaTMsUkbDnjGqSMoO88jWdtJXJV4ST/7DKZcoMHMBvB2SeSfyOzZfkxXHR5xg==
- dependencies:
- "@types/jest-diff" "*"
+ jest-diff "^25.2.1"
+ pretty-format "^25.2.1"
"@types/joi@*", "@types/joi@^13.4.2":
version "13.6.1"
@@ -5095,14 +5310,14 @@
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656"
integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==
-"@types/jsdom@^12.2.4":
- version "12.2.4"
- resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-12.2.4.tgz#845cd4d43f95b8406d9b724ec30c03edadcd9528"
- integrity sha512-q+De3S/Ri6U9uPx89YA1XuC+QIBgndIfvBaaJG0pRT8Oqa75k4Mr7G9CRZjIvlbLGIukO/31DFGFJYlQBmXf/A==
+"@types/jsdom@^16.2.3":
+ version "16.2.3"
+ resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.3.tgz#c6feadfe0836389b27f9c911cde82cd32e91c537"
+ integrity sha512-BREatezSn74rmLIDksuqGNFUTi9HNAWWQXYpFBFLK9U6wlMCO4M0QCa8CMpDsZQuqxSO9XifVLT5Q1P0vgKLqw==
dependencies:
"@types/node" "*"
+ "@types/parse5" "*"
"@types/tough-cookie" "*"
- parse5 "^4.0.0"
"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4":
version "7.0.5"
@@ -5380,6 +5595,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a"
integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==
+"@types/parse5@*":
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
+ integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
+
"@types/pegjs@^0.10.1":
version "0.10.1"
resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.1.tgz#9a2f3961dc62430fdb21061eb0ddbd890f9e3b94"
@@ -5402,6 +5622,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.16.1.tgz#328d1c9b54402e44119398bcb6a31b7bbd606d59"
integrity sha512-db6pZL5QY3JrlCHBhYQzYDci0xnoDuxfseUuguLRr3JNk+bnCfpkK6p8quiUDyO8A0vbpBKkk59Fw125etrNeA==
+"@types/prettier@^1.19.0":
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
+ integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
+
"@types/pretty-ms@^5.0.0":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/pretty-ms/-/pretty-ms-5.0.1.tgz#f2f0d7be58caf8613d149053d446e0282ae11ff3"
@@ -5808,6 +6033,13 @@
dependencies:
pretty-format "^24.3.0"
+"@types/testing-library__jest-dom@^5.0.2", "@types/testing-library__jest-dom@^5.7.0":
+ version "5.7.0"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.7.0.tgz#078790bf4dc89152a74428591a228ec5f9433251"
+ integrity sha512-LoZ3uonlnAbJUz4bg6UoeFl+frfndXngmkCItSjJ8DD5WlRfVqPC5/LgJASsY/dy7AHH2YJ7PcsdASOydcVeFA==
+ dependencies:
+ "@types/jest" "*"
+
"@types/testing-library__react-hooks@^3.0.0", "@types/testing-library__react-hooks@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.1.0.tgz#04d174ce767fbcce3ccb5021d7f156e1b06008a9"
@@ -6010,16 +6242,6 @@
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
integrity sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==
-"@types/yargs-parser@^13.1.0":
- version "13.1.0"
- resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
- integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==
-
-"@types/yargs@^12.0.9":
- version "12.0.10"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.10.tgz#17a8ec65cd8e88f51b418ceb271af18d3137df67"
- integrity sha512-WsVzTPshvCSbHThUduGGxbmnwcpkgSctHGHTqzWyFg4lYAuV5qXlyFPOsP3OWqCINfmg/8VXP+zJaa4OxEsBQQ==
-
"@types/yargs@^13.0.0":
version "13.0.2"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653"
@@ -6332,7 +6554,7 @@ JSONStream@1.3.5:
resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57"
integrity sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=
-abab@^1.0.3, abab@^1.0.4:
+abab@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=
@@ -6402,15 +6624,7 @@ acorn-globals@^3.0.0:
dependencies:
acorn "^4.0.4"
-acorn-globals@^4.0.0:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006"
- integrity sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==
- dependencies:
- acorn "^6.0.1"
- acorn-walk "^6.0.1"
-
-acorn-globals@^4.3.2:
+acorn-globals@^4.3.0, acorn-globals@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==
@@ -6449,7 +6663,7 @@ acorn-walk@^7.0.0, acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e"
integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==
-acorn@5.X, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.5.0:
+acorn@5.X, acorn@^5.0.3, acorn@^5.5.0:
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
@@ -6464,7 +6678,7 @@ acorn@^4.0.4, acorn@~4.0.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
-acorn@^6.0.1, acorn@^6.2.1:
+acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
@@ -6926,7 +7140,7 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
-anymatch@~3.1.1:
+anymatch@^3.0.3, anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
@@ -7827,18 +8041,19 @@ babel-helper-to-multiple-sequence-expressions@^0.5.0:
resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d"
integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==
-babel-jest@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54"
- integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==
+babel-jest@^25.5.1:
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853"
+ integrity sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==
dependencies:
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/babel__core" "^7.1.0"
- babel-plugin-istanbul "^5.1.0"
- babel-preset-jest "^24.9.0"
- chalk "^2.4.2"
- slash "^2.0.0"
+ "@jest/transform" "^25.5.1"
+ "@jest/types" "^25.5.0"
+ "@types/babel__core" "^7.1.7"
+ babel-plugin-istanbul "^6.0.0"
+ babel-preset-jest "^25.5.0"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ slash "^3.0.0"
babel-loader@^8.0.2, babel-loader@^8.0.6:
version "8.0.6"
@@ -7932,11 +8147,24 @@ babel-plugin-istanbul@^5.1.0:
istanbul-lib-instrument "^3.0.0"
test-exclude "^5.0.0"
-babel-plugin-jest-hoist@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756"
- integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==
+babel-plugin-istanbul@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765"
+ integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@istanbuljs/load-nyc-config" "^1.0.0"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-instrument "^4.0.0"
+ test-exclude "^6.0.0"
+
+babel-plugin-jest-hoist@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz#129c80ba5c7fc75baf3a45b93e2e372d57ca2677"
+ integrity sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==
dependencies:
+ "@babel/template" "^7.3.3"
+ "@babel/types" "^7.3.3"
"@types/babel__traverse" "^7.0.6"
babel-plugin-macros@2.6.1:
@@ -8153,13 +8381,29 @@ babel-polyfill@^6.26.0:
core-js "^2.5.0"
regenerator-runtime "^0.10.5"
-babel-preset-jest@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc"
- integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==
+babel-preset-current-node-syntax@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6"
+ integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==
+ dependencies:
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-bigint" "^7.8.3"
+ "@babel/plugin-syntax-class-properties" "^7.8.3"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.8.3"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+babel-preset-jest@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz#c1d7f191829487a907764c65307faa0e66590b49"
+ integrity sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==
dependencies:
- "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
- babel-plugin-jest-hoist "^24.9.0"
+ babel-plugin-jest-hoist "^25.5.0"
+ babel-preset-current-node-syntax "^0.1.2"
"babel-preset-minify@^0.5.0 || 0.6.0-alpha.5":
version "0.5.0"
@@ -8300,25 +8544,26 @@ backo2@1.0.2:
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
-backport@4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/backport/-/backport-4.9.0.tgz#01ca46af57f33f582801e20ef2111b8a2710f8fc"
- integrity sha512-PueA741RIv3mK4mrCoTBa0oB4WTJOOkXlSXQojL/jBqZBfHQ8MRsW8qDygVe/Q9Z6na4gqqieMOZA8qHn8GVVw==
+backport@5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/backport/-/backport-5.4.1.tgz#b066e8bbece91bc813187c13b7bea69ef5355471"
+ integrity sha512-vFR5Juss2pveS2OyyoE5n14j7ZDqeZXakzv4KngTEUTsb+5r/AVj2OG8LfJ14RJBMKBYSf1ojSKgDiWtUi0r+w==
dependencies:
- "@types/yargs-parser" "^13.1.0"
- axios "^0.19.0"
+ axios "^0.19.2"
+ dedent "^0.7.0"
del "^5.1.0"
find-up "^4.1.0"
- inquirer "^7.0.0"
- lodash.flatten "^4.4.0"
- lodash.get "^4.4.2"
+ inquirer "^7.1.0"
+ lodash.flatmap "^4.5.0"
lodash.isempty "^4.4.0"
lodash.isstring "^4.0.1"
- make-dir "^3.0.0"
- ora "^3.4.0"
- strip-json-comments "^3.0.1"
+ lodash.uniq "^4.5.0"
+ make-dir "^3.1.0"
+ ora "^4.0.4"
+ safe-json-stringify "^1.2.0"
+ strip-json-comments "^3.1.0"
winston "^3.2.1"
- yargs "^14.2.0"
+ yargs "^15.3.1"
bail@^1.0.0:
version "1.0.2"
@@ -9364,6 +9609,15 @@ caniuse-lite@^1.0.30001043:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001079.tgz#ed3e5225cd9a6850984fdd88bf24ce45d69b9c22"
integrity sha512-2KaYheg0iOY+CMmDuAB3DHehrXhhb4OZU4KBVGDr/YKyYAcpudaiUQ9PJ9rxrPlKEoJ3ATasQ5AN48MqpwS43Q==
+canvas@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.6.1.tgz#0d087dd4d60f5a5a9efa202757270abea8bef89e"
+ integrity sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==
+ dependencies:
+ nan "^2.14.0"
+ node-pre-gyp "^0.11.0"
+ simple-get "^3.0.3"
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -9889,6 +10143,11 @@ cli-spinners@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7"
integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==
+cli-spinners@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5"
+ integrity sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==
+
cli-table3@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
@@ -10121,6 +10380,11 @@ collapse-white-space@^1.0.2:
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c"
integrity sha1-S5BvZw5aljqHt2sOFolkM0G2Ajw=
+collect-v8-coverage@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz#150ee634ac3650b71d9c985eb7f608942334feb1"
+ integrity sha512-VKIhJgvk8E1W28m5avZ2Gv2Ruv5YiF56ug2oclvaG9md69BuZImMG2sk9g7QNKLUbtYAKQjXjYxbYZVUlMMKmQ==
+
collection-map@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c"
@@ -10612,11 +10876,6 @@ content-disposition@0.5.3:
dependencies:
safe-buffer "5.1.2"
-content-type-parser@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7"
- integrity sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==
-
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@@ -11220,7 +11479,7 @@ css.escape@^1.5.1:
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
-css@2.X, css@^2.0.0, css@^2.2.1, css@^2.2.3, css@^2.2.4:
+css@2.X, css@^2.0.0, css@^2.2.1, css@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
@@ -11247,32 +11506,27 @@ csso@^4.0.2:
dependencies:
css-tree "1.0.0-alpha.37"
-cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
- integrity sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=
+cssom@0.3.x, cssom@^0.3.4, cssom@~0.3.6:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+ integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
cssom@^0.4.1:
version "0.4.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
-cssom@~0.3.6:
- version "0.3.8"
- resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
- integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
-
-"cssstyle@>= 0.2.37 < 0.3.0":
- version "0.2.37"
- resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54"
- integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=
+cssstyle@^1.1.1:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1"
+ integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==
dependencies:
cssom "0.3.x"
cssstyle@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.0.0.tgz#911f0fe25532db4f5d44afc83f89cc4b82c97fe3"
- integrity sha512-QXSAu2WBsSRXCPjvI43Y40m6fMevvyRm8JVAuF9ksQz5jha4pWP1wpaK7Yu5oLFc6+XAY+hj8YhefyXcBB53gg==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992"
+ integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==
dependencies:
cssom "~0.3.6"
@@ -12074,11 +12328,21 @@ detect-indent@^4.0.0:
dependencies:
repeating "^2.0.0"
-detect-newline@2.X, detect-newline@^2.1.0:
+detect-libc@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+ integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+
+detect-newline@2.X:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
+detect-newline@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
+ integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+
detect-node@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
@@ -12213,20 +12477,15 @@ diff-match-patch@^1.0.4:
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
-diff-sequences@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.0.0.tgz#cdf8e27ed20d8b8d3caccb4e0c0d8fe31a173013"
- integrity sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==
-
diff-sequences@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
-diff-sequences@^25.1.0:
- version "25.1.0"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32"
- integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==
+diff-sequences@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
+ integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
diff@3.5.0, diff@^3.5.0:
version "3.5.0"
@@ -12386,11 +12645,6 @@ domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-domexception@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.0.tgz#81fe5df81b3f057052cde3a9fa9bf536a85b9ab0"
- integrity sha512-WpwuBlZ2lQRFa4H/4w49deb9rJLot9KmqrKKjMc9qBl7CID+DdC2swoa34ccRl+anL2B6bLp6TjFdIdnzekMBQ==
-
domexception@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -13242,6 +13496,18 @@ escodegen@1.8.x:
optionalDependencies:
source-map "~0.2.0"
+escodegen@^1.11.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
+ integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==
+ dependencies:
+ esprima "^4.0.1"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ optionator "^0.8.1"
+ optionalDependencies:
+ source-map "~0.6.1"
+
escodegen@^1.11.1:
version "1.12.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541"
@@ -13266,18 +13532,6 @@ escodegen@^1.8.0:
optionalDependencies:
source-map "~0.6.1"
-escodegen@^1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
- integrity sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==
- dependencies:
- esprima "^3.1.3"
- estraverse "^4.2.0"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.5.6"
-
escodegen@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.2.0.tgz#09de7967791cc958b7f89a2ddb6d23451af327e1"
@@ -13401,10 +13655,10 @@ eslint-plugin-import@^2.19.1:
read-pkg-up "^2.0.0"
resolve "^1.12.0"
-eslint-plugin-jest@^23.3.0:
- version "23.3.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.3.0.tgz#b1443d0c46d6a0de9ef3de78176dd6688c7d5326"
- integrity sha512-GE6CR4ESJeu6Huw7vfZfaXHmX2R2kCFvf2X9OMcOxfP158yLKgLWz7PqLYTwRDACi84IhpmRxO8lK7GGwG05UQ==
+eslint-plugin-jest@^23.10.0:
+ version "23.10.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.10.0.tgz#4738c7ca9e6513da50f4e99d7b161c1f82fa8e8f"
+ integrity sha512-cHC//nesojSO1MLxVmFJR/bUaQQG7xvMHQD8YLbsQzevR41WKm8paKDUv2wMHlUy5XLZUmNcWuflOi4apS8D+Q==
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
@@ -13639,7 +13893,7 @@ esprima@^3.1.3, esprima@~3.1.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
-esprima@^4.0.0, esprima@~4.0.0:
+esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
@@ -13839,6 +14093,22 @@ execa@^0.7.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+execa@^3.2.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89"
+ integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==
+ dependencies:
+ cross-spawn "^7.0.0"
+ get-stream "^5.0.0"
+ human-signals "^1.1.1"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.0"
+ onetime "^5.1.0"
+ p-finally "^2.0.0"
+ signal-exit "^3.0.2"
+ strip-final-newline "^2.0.0"
+
execa@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240"
@@ -13925,6 +14195,18 @@ expect@^24.9.0:
jest-message-util "^24.9.0"
jest-regex-util "^24.9.0"
+expect@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-25.5.0.tgz#f07f848712a2813bb59167da3fb828ca21f58bba"
+ integrity sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ ansi-styles "^4.0.0"
+ jest-get-type "^25.2.6"
+ jest-matcher-utils "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-regex-util "^25.2.6"
+
expiry-js@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/expiry-js/-/expiry-js-0.1.7.tgz#76be8c05e572bf936df40c1766448d0b3b2f555f"
@@ -15052,7 +15334,7 @@ fsevents@^1.2.7:
bindings "^1.5.0"
nan "^2.12.1"
-fsevents@~2.1.0, fsevents@~2.1.1, fsevents@~2.1.2:
+fsevents@^2.1.2, fsevents@~2.1.0, fsevents@~2.1.1, fsevents@~2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==
@@ -15898,6 +16180,11 @@ graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==
+graceful-fs@^4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+ integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+
graceful-fs@~1.1:
version "1.1.14"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.1.14.tgz#07078db5f6377f6321fceaaedf497de124dc9465"
@@ -16827,7 +17114,7 @@ html-element-map@^1.2.0:
dependencies:
array-filter "^1.0.0"
-html-encoding-sniffer@^1.0.1, html-encoding-sniffer@^1.0.2:
+html-encoding-sniffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==
@@ -17125,7 +17412,7 @@ icalendar@0.7.1:
resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae"
integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4=
-iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
+iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -17209,6 +17496,13 @@ ignore-by-default@^1.0.1:
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
+ignore-walk@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+ integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+ dependencies:
+ minimatch "^3.0.4"
+
ignore@^3.1.2, ignore@^3.3.5:
version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
@@ -17299,6 +17593,14 @@ import-local@2.0.0, import-local@^2.0.0:
pkg-dir "^3.0.0"
resolve-cwd "^2.0.0"
+import-local@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
+ integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==
+ dependencies:
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
+
imports-loader@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.8.0.tgz#030ea51b8ca05977c40a3abfd9b4088fe0be9a69"
@@ -17590,6 +17892,25 @@ inquirer@^7.0.0:
strip-ansi "^5.1.0"
through "^2.3.6"
+inquirer@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a"
+ integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==
+ dependencies:
+ ansi-escapes "^4.2.1"
+ chalk "^3.0.0"
+ cli-cursor "^3.1.0"
+ cli-width "^2.0.0"
+ external-editor "^3.0.3"
+ figures "^3.0.0"
+ lodash "^4.17.15"
+ mute-stream "0.0.8"
+ run-async "^2.4.0"
+ rxjs "^6.5.3"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+ through "^2.3.6"
+
insight@0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/insight/-/insight-0.10.1.tgz#a0ecf668484a95d66e9be59644964e719cc83380"
@@ -18025,6 +18346,11 @@ is-integer@^1.0.6:
dependencies:
is-finite "^1.0.0"
+is-interactive@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
+ integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+
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"
@@ -18377,6 +18703,11 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+is-wsl@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
+ integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
+
is-yarn-global@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
@@ -18478,7 +18809,7 @@ istanbul-lib-coverage@^1.2.1:
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==
-istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3:
+istanbul-lib-coverage@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba"
integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==
@@ -18488,6 +18819,11 @@ istanbul-lib-coverage@^2.0.5:
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
+istanbul-lib-coverage@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
+ integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
+
istanbul-lib-hook@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
@@ -18508,7 +18844,7 @@ istanbul-lib-instrument@^1.7.3:
istanbul-lib-coverage "^1.2.1"
semver "^5.3.0"
-istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1:
+istanbul-lib-instrument@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971"
integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==
@@ -18534,14 +18870,15 @@ istanbul-lib-instrument@^3.3.0:
istanbul-lib-coverage "^2.0.5"
semver "^6.0.0"
-istanbul-lib-report@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4"
- integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==
+istanbul-lib-instrument@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
+ integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
dependencies:
- istanbul-lib-coverage "^2.0.3"
- make-dir "^1.3.0"
- supports-color "^6.0.0"
+ "@babel/core" "^7.7.5"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-coverage "^3.0.0"
+ semver "^6.3.0"
istanbul-lib-report@^2.0.8:
version "2.0.8"
@@ -18552,16 +18889,14 @@ istanbul-lib-report@^2.0.8:
make-dir "^2.1.0"
supports-color "^6.1.0"
-istanbul-lib-source-maps@^3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156"
- integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==
+istanbul-lib-report@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
+ integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
dependencies:
- debug "^4.1.1"
- istanbul-lib-coverage "^2.0.3"
- make-dir "^1.3.0"
- rimraf "^2.6.2"
- source-map "^0.6.1"
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^3.0.0"
+ supports-color "^7.1.0"
istanbul-lib-source-maps@^3.0.6:
version "3.0.6"
@@ -18574,13 +18909,30 @@ istanbul-lib-source-maps@^3.0.6:
rimraf "^2.6.3"
source-map "^0.6.1"
-istanbul-reports@^2.2.4, istanbul-reports@^2.2.6:
+istanbul-lib-source-maps@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9"
+ integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==
+ dependencies:
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+ source-map "^0.6.1"
+
+istanbul-reports@^2.2.4:
version "2.2.7"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931"
integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==
dependencies:
html-escaper "^2.0.0"
+istanbul-reports@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
+ integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
istanbul@^0.4.0:
version "0.4.5"
resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b"
@@ -18633,58 +18985,84 @@ iterall@^1.2.2:
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
-jest-changed-files@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
- integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==
+jest-changed-files@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c"
+ integrity sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==
dependencies:
- "@jest/types" "^24.9.0"
- execa "^1.0.0"
- throat "^4.0.0"
+ "@jest/types" "^25.5.0"
+ execa "^3.2.0"
+ throat "^5.0.0"
-jest-cli@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af"
- integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==
+jest-circus@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-25.5.4.tgz#d5578346f53d5604b7b1d1e5e56be48b1c73c61d"
+ integrity sha512-nOLJXZjWuV2i8yQ2w+MZ8NhFuqrbgpPAa4mh+DWPYmNI377YYpDKvDUrLMW8U1sa2iGt5mjKQbmJz2SK9AYjWg==
dependencies:
- "@jest/core" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
+ "@babel/traverse" "^7.1.0"
+ "@jest/environment" "^25.5.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ co "^4.6.0"
+ expect "^25.5.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^25.5.0"
+ jest-matcher-utils "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-runtime "^25.5.4"
+ jest-snapshot "^25.5.1"
+ jest-util "^25.5.0"
+ pretty-format "^25.5.0"
+ stack-utils "^1.0.1"
+ throat "^5.0.0"
+
+jest-cli@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.5.4.tgz#b9f1a84d1301a92c5c217684cb79840831db9f0d"
+ integrity sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==
+ dependencies:
+ "@jest/core" "^25.5.4"
+ "@jest/test-result" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
exit "^0.1.2"
- import-local "^2.0.0"
+ graceful-fs "^4.2.4"
+ import-local "^3.0.2"
is-ci "^2.0.0"
- jest-config "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
+ jest-config "^25.5.4"
+ jest-util "^25.5.0"
+ jest-validate "^25.5.0"
prompts "^2.0.1"
- realpath-native "^1.1.0"
- yargs "^13.3.0"
+ realpath-native "^2.0.0"
+ yargs "^15.3.1"
-jest-config@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5"
- integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==
+jest-config@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.5.4.tgz#38e2057b3f976ef7309b2b2c8dcd2a708a67f02c"
+ integrity sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==
dependencies:
"@babel/core" "^7.1.0"
- "@jest/test-sequencer" "^24.9.0"
- "@jest/types" "^24.9.0"
- babel-jest "^24.9.0"
- chalk "^2.0.1"
+ "@jest/test-sequencer" "^25.5.4"
+ "@jest/types" "^25.5.0"
+ babel-jest "^25.5.1"
+ chalk "^3.0.0"
+ deepmerge "^4.2.2"
glob "^7.1.1"
- jest-environment-jsdom "^24.9.0"
- jest-environment-node "^24.9.0"
- jest-get-type "^24.9.0"
- jest-jasmine2 "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- micromatch "^3.1.10"
- pretty-format "^24.9.0"
- realpath-native "^1.1.0"
+ graceful-fs "^4.2.4"
+ jest-environment-jsdom "^25.5.0"
+ jest-environment-node "^25.5.0"
+ jest-get-type "^25.2.6"
+ jest-jasmine2 "^25.5.4"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.5.1"
+ jest-util "^25.5.0"
+ jest-validate "^25.5.0"
+ micromatch "^4.0.2"
+ pretty-format "^25.5.0"
+ realpath-native "^2.0.0"
-jest-diff@*, jest-diff@^24.9.0:
+jest-diff@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
@@ -18694,81 +19072,76 @@ jest-diff@*, jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
-jest-diff@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.0.0.tgz#a3e5f573dbac482f7d9513ac9cfa21644d3d6b34"
- integrity sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==
- dependencies:
- chalk "^2.0.1"
- diff-sequences "^24.0.0"
- jest-get-type "^24.0.0"
- pretty-format "^24.0.0"
-
-jest-diff@^25.1.0:
- version "25.1.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad"
- integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==
+jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
+ integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
dependencies:
chalk "^3.0.0"
- diff-sequences "^25.1.0"
- jest-get-type "^25.1.0"
- pretty-format "^25.1.0"
+ diff-sequences "^25.2.6"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.5.0"
-jest-docblock@^24.3.0:
- version "24.3.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd"
- integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==
+jest-docblock@^25.3.0:
+ version "25.3.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef"
+ integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==
dependencies:
- detect-newline "^2.1.0"
+ detect-newline "^3.0.0"
-jest-each@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05"
- integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==
+jest-each@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.5.0.tgz#0c3c2797e8225cb7bec7e4d249dcd96b934be516"
+ integrity sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==
dependencies:
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- jest-get-type "^24.9.0"
- jest-util "^24.9.0"
- pretty-format "^24.9.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ jest-get-type "^25.2.6"
+ jest-util "^25.5.0"
+ pretty-format "^25.5.0"
-jest-environment-jsdom@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
- integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==
+jest-environment-jsdom-thirteen@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom-thirteen/-/jest-environment-jsdom-thirteen-1.0.1.tgz#113e3c8aed945dadbc826636fa21139c69567bb5"
+ integrity sha512-Zi7OuKF7HMLlBvomitd5eKp5Ykc4Wvw0d+i+cpbCaE+7kmvL24SO4ssDmKrT++aANXR4T8+pmoJIlav5gr2peQ==
dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/fake-timers" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
- jest-util "^24.9.0"
- jsdom "^11.5.1"
+ jest-mock "^24.0.0"
+ jest-util "^24.0.0"
+ jsdom "^13.0.0"
-jest-environment-node@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3"
- integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==
+jest-environment-jsdom@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz#dcbe4da2ea997707997040ecf6e2560aec4e9834"
+ integrity sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==
dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/fake-timers" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
- jest-util "^24.9.0"
+ "@jest/environment" "^25.5.0"
+ "@jest/fake-timers" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ jest-mock "^25.5.0"
+ jest-util "^25.5.0"
+ jsdom "^15.2.1"
-jest-get-type@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.0.0.tgz#36e72930b78e33da59a4f63d44d332188278940b"
- integrity sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==
+jest-environment-node@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.5.0.tgz#0f55270d94804902988e64adca37c6ce0f7d07a1"
+ integrity sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==
+ dependencies:
+ "@jest/environment" "^25.5.0"
+ "@jest/fake-timers" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ jest-mock "^25.5.0"
+ jest-util "^25.5.0"
+ semver "^6.3.0"
jest-get-type@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
-jest-get-type@^25.1.0:
- version "25.1.0"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876"
- integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==
+jest-get-type@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
+ integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
jest-haste-map@^24.9.0:
version "24.9.0"
@@ -18789,45 +19162,56 @@ jest-haste-map@^24.9.0:
optionalDependencies:
fsevents "^1.2.7"
-jest-jasmine2@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0"
- integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==
+jest-haste-map@^25.5.1:
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943"
+ integrity sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ "@types/graceful-fs" "^4.1.2"
+ anymatch "^3.0.3"
+ fb-watchman "^2.0.0"
+ graceful-fs "^4.2.4"
+ jest-serializer "^25.5.0"
+ jest-util "^25.5.0"
+ jest-worker "^25.5.0"
+ micromatch "^4.0.2"
+ sane "^4.0.3"
+ walker "^1.0.7"
+ which "^2.0.2"
+ optionalDependencies:
+ fsevents "^2.1.2"
+
+jest-jasmine2@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz#66ca8b328fb1a3c5364816f8958f6970a8526968"
+ integrity sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==
dependencies:
"@babel/traverse" "^7.1.0"
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
+ "@jest/environment" "^25.5.0"
+ "@jest/source-map" "^25.5.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
co "^4.6.0"
- expect "^24.9.0"
+ expect "^25.5.0"
is-generator-fn "^2.0.0"
- jest-each "^24.9.0"
- jest-matcher-utils "^24.9.0"
- jest-message-util "^24.9.0"
- jest-runtime "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- pretty-format "^24.9.0"
- throat "^4.0.0"
-
-jest-leak-detector@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a"
- integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==
- dependencies:
- jest-get-type "^24.9.0"
- pretty-format "^24.9.0"
-
-jest-matcher-utils@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz#fc9c41cfc49b2c3ec14e576f53d519c37729d579"
- integrity sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==
+ jest-each "^25.5.0"
+ jest-matcher-utils "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-runtime "^25.5.4"
+ jest-snapshot "^25.5.1"
+ jest-util "^25.5.0"
+ pretty-format "^25.5.0"
+ throat "^5.0.0"
+
+jest-leak-detector@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb"
+ integrity sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==
dependencies:
- chalk "^2.0.1"
- jest-diff "^24.0.0"
- jest-get-type "^24.0.0"
- pretty-format "^24.0.0"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.5.0"
jest-matcher-utils@^24.9.0:
version "24.9.0"
@@ -18839,6 +19223,16 @@ jest-matcher-utils@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
+jest-matcher-utils@^25.1.0, jest-matcher-utils@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
+ integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==
+ dependencies:
+ chalk "^3.0.0"
+ jest-diff "^25.5.0"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.5.0"
+
jest-message-util@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3"
@@ -18853,13 +19247,34 @@ jest-message-util@^24.9.0:
slash "^2.0.0"
stack-utils "^1.0.1"
-jest-mock@^24.9.0:
+jest-message-util@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
+ integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@jest/types" "^25.5.0"
+ "@types/stack-utils" "^1.0.1"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ micromatch "^4.0.2"
+ slash "^3.0.0"
+ stack-utils "^1.0.1"
+
+jest-mock@^24.0.0, jest-mock@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6"
integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==
dependencies:
"@jest/types" "^24.9.0"
+jest-mock@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
+ integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
+ dependencies:
+ "@jest/types" "^25.5.0"
+
jest-pnp-resolver@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
@@ -18870,24 +19285,24 @@ jest-raw-loader@^1.0.1:
resolved "https://registry.yarnpkg.com/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz#ce9f56d54650f157c4a7d16d224ba5d613bcd626"
integrity sha1-zp9W1UZQ8VfEp9FtIkul1hO81iY=
-jest-regex-util@^24.3.0:
- version "24.3.0"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36"
- integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==
-
jest-regex-util@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636"
integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==
-jest-resolve-dependencies@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab"
- integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==
+jest-regex-util@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964"
+ integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==
+
+jest-resolve-dependencies@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz#85501f53957c8e3be446e863a74777b5a17397a7"
+ integrity sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==
dependencies:
- "@jest/types" "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-snapshot "^24.9.0"
+ "@jest/types" "^25.5.0"
+ jest-regex-util "^25.2.6"
+ jest-snapshot "^25.5.1"
jest-resolve@^24.9.0:
version "24.9.0"
@@ -18900,66 +19315,91 @@ jest-resolve@^24.9.0:
jest-pnp-resolver "^1.2.1"
realpath-native "^1.1.0"
-jest-runner@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42"
- integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==
+jest-resolve@^25.5.1:
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.5.1.tgz#0e6fbcfa7c26d2a5fe8f456088dc332a79266829"
+ integrity sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==
dependencies:
- "@jest/console" "^24.7.1"
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.4.2"
- exit "^0.1.2"
- graceful-fs "^4.1.15"
- jest-config "^24.9.0"
- jest-docblock "^24.3.0"
- jest-haste-map "^24.9.0"
- jest-jasmine2 "^24.9.0"
- jest-leak-detector "^24.9.0"
- jest-message-util "^24.9.0"
- jest-resolve "^24.9.0"
- jest-runtime "^24.9.0"
- jest-util "^24.9.0"
- jest-worker "^24.6.0"
- source-map-support "^0.5.6"
- throat "^4.0.0"
+ "@jest/types" "^25.5.0"
+ browser-resolve "^1.11.3"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ jest-pnp-resolver "^1.2.1"
+ read-pkg-up "^7.0.1"
+ realpath-native "^2.0.0"
+ resolve "^1.17.0"
+ slash "^3.0.0"
-jest-runtime@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac"
- integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==
+jest-runner@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.5.4.tgz#ffec5df3875da5f5c878ae6d0a17b8e4ecd7c71d"
+ integrity sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==
dependencies:
- "@jest/console" "^24.7.1"
- "@jest/environment" "^24.9.0"
- "@jest/source-map" "^24.3.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/yargs" "^13.0.0"
- chalk "^2.0.1"
+ "@jest/console" "^25.5.0"
+ "@jest/environment" "^25.5.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.4"
+ jest-config "^25.5.4"
+ jest-docblock "^25.3.0"
+ jest-haste-map "^25.5.1"
+ jest-jasmine2 "^25.5.4"
+ jest-leak-detector "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-resolve "^25.5.1"
+ jest-runtime "^25.5.4"
+ jest-util "^25.5.0"
+ jest-worker "^25.5.0"
+ source-map-support "^0.5.6"
+ throat "^5.0.0"
+
+jest-runtime@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.5.4.tgz#dc981fe2cb2137abcd319e74ccae7f7eeffbfaab"
+ integrity sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==
+ dependencies:
+ "@jest/console" "^25.5.0"
+ "@jest/environment" "^25.5.0"
+ "@jest/globals" "^25.5.2"
+ "@jest/source-map" "^25.5.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/transform" "^25.5.1"
+ "@jest/types" "^25.5.0"
+ "@types/yargs" "^15.0.0"
+ chalk "^3.0.0"
+ collect-v8-coverage "^1.0.0"
exit "^0.1.2"
glob "^7.1.3"
- graceful-fs "^4.1.15"
- jest-config "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-message-util "^24.9.0"
- jest-mock "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- realpath-native "^1.1.0"
- slash "^2.0.0"
- strip-bom "^3.0.0"
- yargs "^13.3.0"
+ graceful-fs "^4.2.4"
+ jest-config "^25.5.4"
+ jest-haste-map "^25.5.1"
+ jest-message-util "^25.5.0"
+ jest-mock "^25.5.0"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.5.1"
+ jest-snapshot "^25.5.1"
+ jest-util "^25.5.0"
+ jest-validate "^25.5.0"
+ realpath-native "^2.0.0"
+ slash "^3.0.0"
+ strip-bom "^4.0.0"
+ yargs "^15.3.1"
jest-serializer@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73"
integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==
-jest-snapshot@^24.1.0, jest-snapshot@^24.9.0:
+jest-serializer@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b"
+ integrity sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==
+ dependencies:
+ graceful-fs "^4.2.4"
+
+jest-snapshot@^24.1.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba"
integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==
@@ -18978,6 +19418,27 @@ jest-snapshot@^24.1.0, jest-snapshot@^24.9.0:
pretty-format "^24.9.0"
semver "^6.2.0"
+jest-snapshot@^25.5.1:
+ version "25.5.1"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f"
+ integrity sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==
+ dependencies:
+ "@babel/types" "^7.0.0"
+ "@jest/types" "^25.5.0"
+ "@types/prettier" "^1.19.0"
+ chalk "^3.0.0"
+ expect "^25.5.0"
+ graceful-fs "^4.2.4"
+ jest-diff "^25.5.0"
+ jest-get-type "^25.2.6"
+ jest-matcher-utils "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-resolve "^25.5.1"
+ make-dir "^3.0.0"
+ natural-compare "^1.4.0"
+ pretty-format "^25.5.0"
+ semver "^6.3.0"
+
jest-specific-snapshot@2.0.0, jest-specific-snapshot@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-2.0.0.tgz#425fe524b25df154aa39f97fa6fe9726faaac273"
@@ -18992,7 +19453,7 @@ jest-styled-components@^7.0.2:
dependencies:
css "^2.2.4"
-jest-util@^24.9.0:
+jest-util@^24.0.0, jest-util@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162"
integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==
@@ -19010,32 +19471,42 @@ jest-util@^24.9.0:
slash "^2.0.0"
source-map "^0.6.0"
-jest-validate@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab"
- integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==
+jest-util@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
+ integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
dependencies:
- "@jest/types" "^24.9.0"
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ is-ci "^2.0.0"
+ make-dir "^3.0.0"
+
+jest-validate@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.5.0.tgz#fb4c93f332c2e4cf70151a628e58a35e459a413a"
+ integrity sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
camelcase "^5.3.1"
- chalk "^2.0.1"
- jest-get-type "^24.9.0"
+ chalk "^3.0.0"
+ jest-get-type "^25.2.6"
leven "^3.1.0"
- pretty-format "^24.9.0"
+ pretty-format "^25.5.0"
-jest-watcher@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b"
- integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==
+jest-watcher@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.5.0.tgz#d6110d101df98badebe435003956fd4a465e8456"
+ integrity sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==
dependencies:
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/yargs" "^13.0.0"
- ansi-escapes "^3.0.0"
- chalk "^2.0.1"
- jest-util "^24.9.0"
- string-length "^2.0.0"
+ "@jest/test-result" "^25.5.0"
+ "@jest/types" "^25.5.0"
+ ansi-escapes "^4.2.1"
+ chalk "^3.0.0"
+ jest-util "^25.5.0"
+ string-length "^3.1.0"
-jest-worker@^24.6.0, jest-worker@^24.9.0:
+jest-worker@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
@@ -19043,7 +19514,7 @@ jest-worker@^24.6.0, jest-worker@^24.9.0:
merge-stream "^2.0.0"
supports-color "^6.1.0"
-jest-worker@^25.4.0:
+jest-worker@^25.4.0, jest-worker@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1"
integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==
@@ -19051,13 +19522,14 @@ jest-worker@^25.4.0:
merge-stream "^2.0.0"
supports-color "^7.0.0"
-jest@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171"
- integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==
+jest@^25.5.4:
+ version "25.5.4"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db"
+ integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==
dependencies:
- import-local "^2.0.0"
- jest-cli "^24.9.0"
+ "@jest/core" "^25.5.4"
+ import-local "^3.0.2"
+ jest-cli "^25.5.4"
jimp@^0.9.6:
version "0.9.6"
@@ -19153,35 +19625,37 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-jsdom@^11.5.1:
- version "11.5.1"
- resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.5.1.tgz#5df753b8d0bca20142ce21f4f6c039f99a992929"
- integrity sha512-89ztIZ03aYK9f1uUrLXLsZndRge/JnZjzjpaN+lrse3coqz+8PR/dX4WLHpbF5fIKTXhDjFODOJw2328lPJ90g==
+jsdom@13.1.0, jsdom@^13.0.0:
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-13.1.0.tgz#fa7356f0cc8111d0f1077cb7800d06f22f1d66c7"
+ integrity sha512-C2Kp0qNuopw0smXFaHeayvharqF3kkcNqlcIlSX71+3XrsOFwkEPLt/9f5JksMmaul2JZYIQuY+WTpqHpQQcLg==
dependencies:
- abab "^1.0.3"
- acorn "^5.1.2"
- acorn-globals "^4.0.0"
+ abab "^2.0.0"
+ acorn "^6.0.4"
+ acorn-globals "^4.3.0"
array-equal "^1.0.0"
- browser-process-hrtime "^0.1.2"
- content-type-parser "^1.0.1"
- cssom ">= 0.3.2 < 0.4.0"
- cssstyle ">= 0.2.37 < 0.3.0"
- domexception "^1.0.0"
- escodegen "^1.9.0"
- html-encoding-sniffer "^1.0.1"
- left-pad "^1.2.0"
- nwmatcher "^1.4.3"
- parse5 "^3.0.2"
- pn "^1.0.0"
- request "^2.83.0"
- request-promise-native "^1.0.3"
- sax "^1.2.1"
- symbol-tree "^3.2.1"
- tough-cookie "^2.3.3"
+ cssom "^0.3.4"
+ cssstyle "^1.1.1"
+ data-urls "^1.1.0"
+ domexception "^1.0.1"
+ escodegen "^1.11.0"
+ html-encoding-sniffer "^1.0.2"
+ nwsapi "^2.0.9"
+ parse5 "5.1.0"
+ pn "^1.1.0"
+ request "^2.88.0"
+ request-promise-native "^1.0.5"
+ saxes "^3.1.4"
+ symbol-tree "^3.2.2"
+ tough-cookie "^2.5.0"
+ w3c-hr-time "^1.0.1"
+ w3c-xmlserializer "^1.0.1"
webidl-conversions "^4.0.2"
- whatwg-encoding "^1.0.1"
- whatwg-url "^6.3.0"
- xml-name-validator "^2.0.1"
+ whatwg-encoding "^1.0.5"
+ whatwg-mimetype "^2.3.0"
+ whatwg-url "^7.0.0"
+ ws "^6.1.2"
+ xml-name-validator "^3.0.0"
jsdom@^15.2.1:
version "15.2.1"
@@ -19864,11 +20338,6 @@ leaflet@1.5.1:
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.5.1.tgz#9afb9d963d66c870066b1342e7a06f92840f46bf"
integrity sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==
-left-pad@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee"
- integrity sha1-0wpzxrggHY99jnlWupYWCHpo4O4=
-
less-loader@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-5.0.0.tgz#498dde3a6c6c4f887458ee9ed3f086a12ad1b466"
@@ -20282,6 +20751,11 @@ lodash.filter@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
+lodash.flatmap@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e"
+ integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=
+
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@@ -20502,7 +20976,7 @@ lodash.uniqby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=
-lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
+lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -20532,7 +21006,7 @@ log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
dependencies:
chalk "^2.0.1"
-log-symbols@3.0.0:
+log-symbols@3.0.0, log-symbols@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
@@ -20598,11 +21072,18 @@ loglevelnext@^1.0.1:
es6-symbol "^3.1.1"
object.assign "^4.1.0"
-lolex@^4.1.0, lolex@^4.2.0:
+lolex@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7"
integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==
+lolex@^5.0.0, lolex@^5.0.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
+ integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
+ dependencies:
+ "@sinonjs/commons" "^1.7.0"
+
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@@ -20748,7 +21229,7 @@ majo@^0.4.1:
globby "^6.1.0"
ware "^1.3.0"
-make-dir@^1.0.0, make-dir@^1.3.0:
+make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
@@ -20763,7 +21244,7 @@ make-dir@^2.0.0, make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
-make-dir@^3.0.0, make-dir@^3.0.2:
+make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -21810,7 +22291,7 @@ nan@^2.12.1, nan@^2.13.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
-nan@^2.14.1:
+nan@^2.14.0, nan@^2.14.1:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
@@ -21875,6 +22356,15 @@ nearley@^2.7.10:
randexp "0.4.6"
semver "^5.4.1"
+needle@^2.2.1:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0"
+ integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==
+ dependencies:
+ debug "^3.2.6"
+ iconv-lite "^0.4.4"
+ sax "^1.2.4"
+
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -21961,14 +22451,14 @@ nigel@3.x.x:
vise "3.x.x"
nise@^1.5.2:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.2.tgz#b6d29af10e48b321b307e10e065199338eeb2652"
- integrity sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
+ integrity sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==
dependencies:
"@sinonjs/formatio" "^3.2.1"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
- lolex "^4.1.0"
+ lolex "^5.0.1"
path-to-regexp "^1.7.0"
no-case@^2.2.0, no-case@^2.3.2:
@@ -22165,16 +22655,32 @@ node-modules-regexp@^1.0.0:
resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
-node-notifier@^5.4.2:
- version "5.4.3"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
- integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
+node-notifier@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12"
+ integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==
dependencies:
growly "^1.3.0"
- is-wsl "^1.1.0"
- semver "^5.5.0"
+ is-wsl "^2.1.1"
+ semver "^6.3.0"
shellwords "^0.1.1"
- which "^1.3.0"
+ which "^1.3.1"
+
+node-pre-gyp@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
+ integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
+ dependencies:
+ detect-libc "^1.0.2"
+ mkdirp "^0.5.1"
+ needle "^2.2.1"
+ nopt "^4.0.1"
+ npm-packlist "^1.1.6"
+ npmlog "^4.0.2"
+ rc "^1.2.7"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^4"
node-releases@^1.1.25, node-releases@^1.1.46:
version "1.1.47"
@@ -22361,6 +22867,13 @@ now-and-later@^2.0.0:
dependencies:
once "^1.3.2"
+npm-bundled@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
+ integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
+ dependencies:
+ npm-normalize-package-bin "^1.0.1"
+
npm-conf@^1.1.0, npm-conf@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
@@ -22377,6 +22890,20 @@ npm-keyword@^5.0.0:
got "^7.1.0"
registry-url "^3.0.3"
+npm-normalize-package-bin@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
+ integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
+
+npm-packlist@^1.1.6:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
+ integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
+ dependencies:
+ ignore-walk "^3.0.1"
+ npm-bundled "^1.0.1"
+ npm-normalize-package-bin "^1.0.1"
+
npm-run-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f"
@@ -22414,7 +22941,7 @@ npmconf@^2.1.3:
semver "2 || 3 || 4"
uid-number "0.0.5"
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.1.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -22468,12 +22995,7 @@ numeral@^2.0.6:
resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506"
integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY=
-nwmatcher@^1.4.3:
- version "1.4.4"
- resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
- integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
-
-nwsapi@^2.2.0:
+nwsapi@^2.0.9, nwsapi@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
@@ -22881,7 +23403,7 @@ ora@^1.3.0, ora@^1.4.0:
cli-spinners "^1.0.1"
log-symbols "^2.1.0"
-ora@^3.0.0, ora@^3.4.0:
+ora@^3.0.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
@@ -22893,6 +23415,20 @@ ora@^3.0.0, ora@^3.4.0:
strip-ansi "^5.2.0"
wcwidth "^1.0.1"
+ora@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.4.tgz#e8da697cc5b6a47266655bf68e0fb588d29a545d"
+ integrity sha512-77iGeVU1cIdRhgFzCK8aw1fbtT1B/iZAvWjS+l/o1x0RShMgxHUZaD2yDpWsNCPwXg9z1ZA78Kbdvr8kBmG/Ww==
+ dependencies:
+ chalk "^3.0.0"
+ cli-cursor "^3.1.0"
+ cli-spinners "^2.2.0"
+ is-interactive "^1.0.0"
+ log-symbols "^3.0.0"
+ mute-stream "0.0.8"
+ strip-ansi "^6.0.0"
+ wcwidth "^1.0.1"
+
ordered-read-streams@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
@@ -23027,12 +23563,10 @@ p-defer@^1.0.0:
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
-p-each-series@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
- integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=
- dependencies:
- p-reduce "^1.0.0"
+p-each-series@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
+ integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
p-event@^4.0.0, p-event@^4.1.0:
version "4.1.0"
@@ -23046,6 +23580,11 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+p-finally@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
+ integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==
+
p-is-promise@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
@@ -23115,11 +23654,6 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
-p-reduce@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
- integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
-
p-retry@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
@@ -23414,18 +23948,13 @@ parse5@5.1.0, parse5@^5.0.0:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
-parse5@^3.0.1, parse5@^3.0.2:
+parse5@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
dependencies:
"@types/node" "*"
-parse5@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
- integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
-
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@@ -23784,7 +24313,7 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
-pkg-dir@^4.1.0:
+pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
@@ -23842,7 +24371,7 @@ pluralize@^7.0.0:
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==
-pn@^1.0.0, pn@^1.1.0:
+pn@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
@@ -24109,16 +24638,6 @@ pretty-error@^2.1.1:
renderkid "^2.0.1"
utila "~0.4"
-pretty-format@^24.0.0:
- version "24.8.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
- integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
- dependencies:
- "@jest/types" "^24.8.0"
- ansi-regex "^4.0.0"
- ansi-styles "^3.2.0"
- react-is "^16.8.4"
-
pretty-format@^24.3.0, pretty-format@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
@@ -24129,12 +24648,12 @@ pretty-format@^24.3.0, pretty-format@^24.9.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
-pretty-format@^25.1.0:
- version "25.1.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8"
- integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==
+pretty-format@^25.2.1, pretty-format@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+ integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
dependencies:
- "@jest/types" "^25.1.0"
+ "@jest/types" "^25.5.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
@@ -25894,6 +26413,11 @@ realpath-native@^1.1.0:
dependencies:
util.promisify "^1.0.0"
+realpath-native@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866"
+ integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==
+
recast@^0.14.7:
version "0.14.7"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d"
@@ -26397,13 +26921,6 @@ request-progress@3.0.0:
dependencies:
throttleit "^1.0.0"
-request-promise-core@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6"
- integrity sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=
- dependencies:
- lodash "^4.13.1"
-
request-promise-core@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346"
@@ -26418,16 +26935,7 @@ request-promise-core@1.1.3:
dependencies:
lodash "^4.17.15"
-request-promise-native@^1.0.3:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5"
- integrity sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=
- dependencies:
- request-promise-core "1.1.1"
- stealthy-require "^1.1.0"
- tough-cookie ">=2.3.3"
-
-request-promise-native@^1.0.7:
+request-promise-native@^1.0.5, request-promise-native@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36"
integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==
@@ -26500,7 +27008,7 @@ request@2.88.0, request@^2.88.0:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
-request@^2.74.0, request@^2.83.0:
+request@^2.74.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
integrity sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==
@@ -26663,6 +27171,13 @@ resolve-cwd@^2.0.0:
dependencies:
resolve-from "^3.0.0"
+resolve-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
+
resolve-dependency-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz#11700e340717b865d216c66cabeb4a2a3c696736"
@@ -26755,7 +27270,7 @@ resolve@1.8.1:
dependencies:
path-parse "^1.0.5"
-resolve@^1.1.10, resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.7.1, resolve@^1.8.1:
+resolve@^1.1.10, resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.7.1, resolve@^1.8.1:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
@@ -27012,6 +27527,11 @@ run-async@^2.0.0, run-async@^2.2.0:
dependencies:
is-promise "^2.1.0"
+run-async@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
+ integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
+
run-parallel@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
@@ -27056,17 +27576,17 @@ rx@^4.1.0:
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
-rxjs-marbles@^5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/rxjs-marbles/-/rxjs-marbles-5.0.3.tgz#d3ca62a4e02d032b1b4ffd558e93336ad78fd100"
- integrity sha512-JK6EvLe9uReJxBmUgdKrpMB2JswV+fDcKDg97x20LErLQ7Gi0FG3YEr2Uq9hvgHJjgZXGCvonpzcxARLzKsT4A==
+rxjs-marbles@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/rxjs-marbles/-/rxjs-marbles-5.0.6.tgz#e8e71df3b82b49603555f017f2fd3d8c359c4c24"
+ integrity sha512-VEcWhSR2QwgmpOh4iL/8BtSqXwnfSsM9JjCVVG/NXbHOuPIzLQ/RJpgvdt0eHixP5mjLof2VODVR62XRCdOqgA==
dependencies:
fast-equals "^2.0.0"
-rxjs@6.5.2:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
- integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
+rxjs@6.5.5, rxjs@^6.5.3, rxjs@^6.5.5:
+ version "6.5.5"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
+ integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
@@ -27077,7 +27597,7 @@ rxjs@^5.5.2:
dependencies:
symbol-observable "1.0.1"
-rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.3:
+rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1:
version "6.5.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==
@@ -27104,6 +27624,11 @@ safe-json-parse@~1.0.1:
resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=
+safe-json-stringify@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
+ integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -27214,12 +27739,12 @@ sass-lookup@^3.0.0:
dependencies:
commander "^2.16.0"
-sax@>=0.6.0, sax@^1.2.1, sax@~1.2.4:
+sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-saxes@^3.1.9:
+saxes@^3.1.4, saxes@^3.1.9:
version "3.1.11"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b"
integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==
@@ -27724,6 +28249,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+simple-concat@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
+ integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
+
+simple-get@^3.0.3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
+ integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
+ dependencies:
+ decompress-response "^4.2.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
simple-git@1.116.0:
version "1.116.0"
resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.116.0.tgz#ea6e533466f1e0152186e306e004d4eefa6e3e00"
@@ -27779,9 +28318,9 @@ simplify-js@^1.2.1:
integrity sha512-0IkEqs+5c5vROkHaifGfbqHf5tYDcsTBy6oJPRbFCSwp2uzEr+PpH3dNP7wD8O3d7zdUCjLVq1/xHkwA/JjlFA==
sinon@^7.4.2:
- version "7.4.2"
- resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.4.2.tgz#ecd54158fef2fcfbdb231a3fa55140e8cb02ad6c"
- integrity sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ==
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.5.0.tgz#e9488ea466070ea908fd44a3d6478fd4923c67ec"
+ integrity sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==
dependencies:
"@sinonjs/commons" "^1.4.0"
"@sinonjs/formatio" "^3.2.1"
@@ -28048,11 +28587,16 @@ source-map@^0.4.2:
dependencies:
amdefine ">=0.0.4"
-source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
+source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
+source-map@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
+ integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+
source-map@~0.1.30:
version "0.1.43"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
@@ -28421,7 +28965,7 @@ stdout-stream@^1.4.0:
dependencies:
readable-stream "^2.0.1"
-stealthy-require@^1.1.0, stealthy-require@^1.1.1:
+stealthy-require@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
@@ -28518,6 +29062,14 @@ string-length@^2.0.0:
astral-regex "^1.0.0"
strip-ansi "^4.0.0"
+string-length@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837"
+ integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==
+ dependencies:
+ astral-regex "^1.0.0"
+ strip-ansi "^5.2.0"
+
string-replace-loader@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-2.2.0.tgz#0a0e6543fcec783d85c353a3e96a23872d45a94f"
@@ -28787,6 +29339,11 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
+strip-json-comments@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
+ integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
+
strip-json-comments@~1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
@@ -28936,7 +29493,7 @@ supports-color@6.0.0:
dependencies:
has-flag "^3.0.0"
-supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0:
+supports-color@6.1.0, supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
@@ -29063,7 +29620,7 @@ symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4, sy
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
-symbol-tree@^3.2.1, symbol-tree@^3.2.2:
+symbol-tree@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
@@ -29239,7 +29796,7 @@ tar-stream@^2.1.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
-tar@4.4.13, tar@^4.4.12:
+tar@4.4.13, tar@^4, tar@^4.4.12:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
@@ -29356,7 +29913,7 @@ term-size@^2.1.0:
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==
-terminal-link@^2.1.1:
+terminal-link@^2.0.0, terminal-link@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
@@ -29413,6 +29970,15 @@ test-exclude@^5.0.0, test-exclude@^5.2.3:
read-pkg-up "^4.0.0"
require-main-filename "^2.0.0"
+test-exclude@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
+ integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
+ dependencies:
+ "@istanbuljs/schema" "^0.1.2"
+ glob "^7.1.4"
+ minimatch "^3.0.4"
+
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
@@ -29437,10 +30003,10 @@ thread-loader@^2.1.3:
loader-utils "^1.1.0"
neo-async "^2.6.0"
-throat@^4.0.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
- integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
+throat@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
+ integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
throttle-debounce@^2.1.0:
version "2.1.0"
@@ -29822,7 +30388,7 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
-tough-cookie@>=2.3.3, tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.4.3:
+tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
@@ -29830,6 +30396,14 @@ tough-cookie@>=2.3.3, tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.
psl "^1.1.24"
punycode "^1.4.1"
+tough-cookie@^2.5.0, tough-cookie@~2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+ integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+ dependencies:
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
tough-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
@@ -29846,15 +30420,7 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3:
dependencies:
punycode "^1.4.1"
-tough-cookie@~2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
- dependencies:
- psl "^1.1.28"
- punycode "^2.1.1"
-
-tr46@^1.0.0, tr46@^1.0.1:
+tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
@@ -31322,6 +31888,15 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
+v8-to-istanbul@^4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz#22fe35709a64955f49a08a7c7c959f6520ad6f20"
+ integrity sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.1"
+ convert-source-map "^1.6.0"
+ source-map "^0.7.3"
+
v8flags@^3.0.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8"
@@ -31964,7 +32539,7 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
-w3c-xmlserializer@^1.1.2:
+w3c-xmlserializer@^1.0.1, w3c-xmlserializer@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794"
integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==
@@ -32060,7 +32635,7 @@ web-namespaces@^1.1.2:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4"
integrity sha512-II+n2ms4mPxK+RnIxRPOw3zwF2jRscdJIUE9BfkKHm4FYEg9+biIoTMnaZF5MpemE3T+VhMLrhbyD4ilkPCSbg==
-webidl-conversions@^4.0.1, webidl-conversions@^4.0.2:
+webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
@@ -32269,15 +32844,6 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
-whatwg-url@^6.3.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08"
- integrity sha512-Z0CVh/YE217Foyb488eo+iBv+r7eAQ0wSTyApi9n06jhcA3z6Nidg/EGvl0UFkg7kMdKxfBzzr+o9JF+cevgMg==
- dependencies:
- lodash.sortby "^4.7.0"
- tr46 "^1.0.0"
- webidl-conversions "^4.0.1"
-
whatwg-url@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
@@ -32306,14 +32872,14 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@1, which@1.3.1, which@^1.2.9, which@^1.3.1, which@~1.3.0:
+which@1, which@1.3.1, which@^1.2.9, which@^1.3.0, which@^1.3.1, which@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
-which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.8, which@^1.3.0:
+which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.8:
version "1.3.0"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==
@@ -32327,6 +32893,13 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
+which@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
wide-align@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
@@ -32602,7 +33175,7 @@ ws@^6.1.0:
dependencies:
async-limiter "~1.0.0"
-ws@^6.2.1:
+ws@^6.1.2, ws@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
@@ -32675,11 +33248,6 @@ xml-crypto@^1.4.0:
xmldom "0.1.27"
xpath "0.0.27"
-xml-name-validator@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
- integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=
-
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
@@ -32842,14 +33410,6 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs-parser@^15.0.1:
- version "15.0.1"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3"
- integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==
- dependencies:
- camelcase "^5.0.0"
- decamelize "^1.2.0"
-
yargs-parser@^18.1.1:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -32946,23 +33506,6 @@ yargs@4.8.1:
y18n "^3.2.1"
yargs-parser "^2.4.1"
-yargs@^14.2.0:
- version "14.2.3"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414"
- integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==
- dependencies:
- cliui "^5.0.0"
- decamelize "^1.2.0"
- find-up "^3.0.0"
- get-caller-file "^2.0.1"
- require-directory "^2.1.1"
- require-main-filename "^2.0.0"
- set-blocking "^2.0.0"
- string-width "^3.0.0"
- which-module "^2.0.0"
- y18n "^4.0.0"
- yargs-parser "^15.0.1"
-
yargs@^15.1.0, yargs@^15.3.1, yargs@~15.3.1:
version "15.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
From e7fd974b6f7bc75bb0847fa9c16c15a18b71d56f Mon Sep 17 00:00:00 2001
From: Spencer
Date: Sat, 20 Jun 2020 18:46:35 -0700
Subject: [PATCH 27/33] [ftr] add support for docker servers (#68173) (#69593)
Co-authored-by: spalger
Co-authored-by: spalger
---
packages/kbn-dev-utils/src/index.ts | 1 +
.../kbn-dev-utils/src/proc_runner/proc.ts | 2 +-
packages/kbn-dev-utils/src/stdio/index.ts | 21 ++
.../{proc_runner => stdio}/observe_lines.ts | 0
.../observe_readable.ts | 0
packages/kbn-test/package.json | 9 +-
.../src/functional_test_runner/cli.ts | 6 +-
.../functional_test_runner.ts | 8 +
.../src/functional_test_runner/index.ts | 1 +
.../lib/config/schema.ts | 23 ++
.../lib/docker_servers/README.md | 113 +++++++++
.../lib/docker_servers/container_logs.ts | 43 ++++
.../lib/docker_servers/container_running.ts | 65 +++++
.../define_docker_servers_config.ts | 45 ++++
.../docker_servers/docker_servers_service.ts | 224 ++++++++++++++++++
.../lib/docker_servers/index.ts | 21 ++
.../src/functional_test_runner/lib/index.ts | 1 +
.../lib/lifecycle_phase.ts | 32 ++-
packages/kbn-test/src/index.ts | 1 +
packages/kbn-test/types/ftr.d.ts | 12 +-
test/functional/config.js | 1 +
vars/workers.groovy | 10 +-
x-pack/test/functional/config.js | 2 -
23 files changed, 616 insertions(+), 25 deletions(-)
create mode 100644 packages/kbn-dev-utils/src/stdio/index.ts
rename packages/kbn-dev-utils/src/{proc_runner => stdio}/observe_lines.ts (100%)
rename packages/kbn-dev-utils/src/{proc_runner => stdio}/observe_readable.ts (100%)
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_logs.ts
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_running.ts
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/define_docker_servers_config.ts
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/docker_servers_service.ts
create mode 100644 packages/kbn-test/src/functional_test_runner/lib/docker_servers/index.ts
diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts
index 40ee72d94729..fad5f85ab589 100644
--- a/packages/kbn-dev-utils/src/index.ts
+++ b/packages/kbn-dev-utils/src/index.ts
@@ -37,4 +37,5 @@ export { run, createFailError, createFlagError, combineErrors, isFailError, Flag
export { REPO_ROOT } from './repo_root';
export { KbnClient } from './kbn_client';
export * from './axios';
+export * from './stdio';
export * from './ci_stats_reporter';
diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts
index 59512cbb133b..bd2368defd7e 100644
--- a/packages/kbn-dev-utils/src/proc_runner/proc.ts
+++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts
@@ -29,7 +29,7 @@ import { promisify } from 'util';
const treeKillAsync = promisify((...args: [number, string, any]) => treeKill(...args));
import { ToolingLog } from '../tooling_log';
-import { observeLines } from './observe_lines';
+import { observeLines } from '../stdio';
import { createCliError } from './errors';
const SECOND = 1000;
diff --git a/packages/kbn-dev-utils/src/stdio/index.ts b/packages/kbn-dev-utils/src/stdio/index.ts
new file mode 100644
index 000000000000..3436d971b879
--- /dev/null
+++ b/packages/kbn-dev-utils/src/stdio/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+export * from './observe_lines';
+export * from './observe_readable';
diff --git a/packages/kbn-dev-utils/src/proc_runner/observe_lines.ts b/packages/kbn-dev-utils/src/stdio/observe_lines.ts
similarity index 100%
rename from packages/kbn-dev-utils/src/proc_runner/observe_lines.ts
rename to packages/kbn-dev-utils/src/stdio/observe_lines.ts
diff --git a/packages/kbn-dev-utils/src/proc_runner/observe_readable.ts b/packages/kbn-dev-utils/src/stdio/observe_readable.ts
similarity index 100%
rename from packages/kbn-dev-utils/src/proc_runner/observe_readable.ts
rename to packages/kbn-dev-utils/src/stdio/observe_readable.ts
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index 2ab27f048eda..042de2617565 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -1,9 +1,9 @@
{
"name": "@kbn/test",
- "main": "./target/index.js",
"version": "1.0.0",
- "license": "Apache-2.0",
"private": true,
+ "license": "Apache-2.0",
+ "main": "./target/index.js",
"scripts": {
"build": "babel src --out-dir target --delete-dir-on-start --extensions .ts,.js,.tsx --ignore *.test.js,**/__tests__/** --source-maps=inline",
"kbn:bootstrap": "yarn build",
@@ -13,6 +13,7 @@
"@babel/cli": "^7.10.1",
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
+ "@types/joi": "^13.4.2",
"@types/parse-link-header": "^1.0.0",
"@types/puppeteer": "^3.0.0",
"@types/strip-ansi": "^5.2.1",
@@ -23,12 +24,14 @@
"chalk": "^2.4.2",
"dedent": "^0.7.0",
"del": "^5.1.0",
+ "exit-hook": "^2.2.0",
"getopts": "^2.2.4",
"glob": "^7.1.2",
+ "joi": "^13.5.2",
"parse-link-header": "^1.0.1",
"puppeteer": "^3.3.0",
- "strip-ansi": "^5.2.0",
"rxjs": "^6.5.5",
+ "strip-ansi": "^5.2.0",
"tar-fs": "^1.16.3",
"tmp": "^0.1.0",
"xml2js": "^0.4.22",
diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts
index c8ccb09903f0..021562333e59 100644
--- a/packages/kbn-test/src/functional_test_runner/cli.ts
+++ b/packages/kbn-test/src/functional_test_runner/cli.ts
@@ -19,7 +19,10 @@
import { resolve } from 'path';
import { inspect } from 'util';
+
import { run, createFlagError, Flags } from '@kbn/dev-utils';
+import exitHook from 'exit-hook';
+
import { FunctionalTestRunner } from './functional_test_runner';
const makeAbsolutePath = (v: string) => resolve(process.cwd(), v);
@@ -84,8 +87,7 @@ export function runFtrCli() {
err instanceof Error ? err : new Error(`non-Error type rejection value: ${inspect(err)}`)
)
);
- process.on('SIGTERM', () => teardown());
- process.on('SIGINT', () => teardown());
+ exitHook(teardown);
try {
if (flags['test-stats']) {
diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts
index 03d4d7643607..24f5cdceac95 100644
--- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts
+++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts
@@ -29,6 +29,7 @@ import {
readProviderSpec,
setupMocha,
runTests,
+ DockerServersService,
Config,
SuiteTracker,
} from './lib';
@@ -130,12 +131,19 @@ export class FunctionalTestRunner {
throw new Error('No tests defined.');
}
+ const dockerServers = new DockerServersService(
+ config.get('dockerServers'),
+ this.log,
+ this.lifecycle
+ );
+
// base level services that functional_test_runner exposes
const coreProviders = readProviderSpec('Service', {
lifecycle: () => this.lifecycle,
log: () => this.log,
failureMetadata: () => this.failureMetadata,
config: () => config,
+ dockerServers: () => dockerServers,
});
return await handler(config, coreProviders);
diff --git a/packages/kbn-test/src/functional_test_runner/index.ts b/packages/kbn-test/src/functional_test_runner/index.ts
index c783117a0ba4..cf65ceb51df8 100644
--- a/packages/kbn-test/src/functional_test_runner/index.ts
+++ b/packages/kbn-test/src/functional_test_runner/index.ts
@@ -20,3 +20,4 @@
export { FunctionalTestRunner } from './functional_test_runner';
export { readConfigFile } from './lib';
export { runFtrCli } from './cli';
+export * from './lib/docker_servers';
diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
index e9aeee87f1a3..6cbdc5ec7fc2 100644
--- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
+++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
@@ -57,6 +57,27 @@ const appUrlPartsSchema = () =>
})
.default();
+const requiredWhenEnabled = (schema: Joi.Schema) => {
+ return Joi.when('enabled', {
+ is: true,
+ then: schema.required(),
+ otherwise: schema.optional(),
+ });
+};
+
+const dockerServerSchema = () =>
+ Joi.object()
+ .keys({
+ enabled: Joi.boolean().required(),
+ image: requiredWhenEnabled(Joi.string()),
+ port: requiredWhenEnabled(Joi.number()),
+ portInContainer: requiredWhenEnabled(Joi.number()),
+ waitForLogLine: Joi.alternatives(Joi.object().type(RegExp), Joi.string()).optional(),
+ waitFor: Joi.func().optional(),
+ args: Joi.array().items(Joi.string()).optional(),
+ })
+ .default();
+
const defaultRelativeToConfigPath = (path: string) => {
const makeDefault: any = (_: any, options: any) => resolve(dirname(options.context.path), path);
makeDefault.description = `/${path}`;
@@ -259,5 +280,7 @@ export const schema = Joi.object()
disableTestUser: Joi.boolean(),
})
.default(),
+
+ dockerServers: Joi.object().pattern(Joi.string(), dockerServerSchema()).default(),
})
.default();
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md
new file mode 100644
index 000000000000..d75faf4c854a
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md
@@ -0,0 +1,113 @@
+# Functional Test Runner - Docker Servers
+
+In order to make it simpler to run some services while the functional tests are running, we've added the ability to execute docker containers while the tests execute for the purpose of exposing services to the tests. These containers are expected to expose an application via a single HTTP port and live for the life of the tests. If the application exits for any reason before the tests complete the tests will abort.
+
+To configure docker servers in your FTR config add the `dockerServers` key to your config like so:
+
+```ts
+// import this helper to get TypeScript support for this section of the config
+import { defineDockerServersConfig } from '@kbn/test';
+
+export default function () {
+ return {
+ ...
+
+ dockerServers: defineDockerServersConfig({
+ // unique names are used in logging and to get the details of this server in the tests
+ helloWorld: {
+ /** disable this docker server unless the user sets some flag/env var */
+ enabled: !!process.env.HELLO_WORLD_PORT,
+ /** the docker image to pull and run */
+ image: 'vad1mo/hello-world-rest',
+ /** The port that this application will be accessible via locally */
+ port: process.env.HELLO_WORLD_PORT,
+ /** The port that the container binds to in the container */
+ portInContainer: 5050,
+ /**
+ * OPTIONAL: string/regex to look for in the log, when specified the
+ * tests won't start until a line containing this string, or matching
+ * this expression is found.
+ */
+ waitForLogLine: /hello/,
+ /**
+ * OPTIONAL: function that is called when server is started, when defined
+ * it is called to give the configuration an option to write custom delay
+ * logic. The function is passed a DockerServer object, which is described
+ * below, and an observable of the log lines produced by the application
+ */
+ async waitFor(server, logLine$) {
+ await logLine$.pipe(
+ filter(line => line.includes('...')),
+ tap((line) => {
+ console.log('marking server ready because this line was logged:', line);
+ console.log('server accessible from url', server.url);
+ })
+ ).toPromise()
+ }
+ }
+ })
+ }
+}
+```
+
+To consume the test server, use can use something like supertest to send request. Just make sure that you disable your test suite if the user doesn't choose to enable your docker server:
+
+```ts
+import makeSupertest from 'supertest-as-promised';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export default function ({ getService }: FtrProviderContext) {
+ const dockerServers = getService('dockerServers');
+ const log = getService('log');
+
+ const server = dockerServers.get('helloWorld');
+ const supertest = makeSupertest(server.url);
+
+ describe('test suite name', function () {
+ if (!server.enabled) {
+ log.warning(
+ 'disabling tests because server is not enabled, set HELLO_WORLD_PORT to run them'
+ );
+ this.pending = true;
+ }
+
+ it('test name', async () => {
+ await supertest.get('/foo/bar').expect(200);
+ });
+ });
+}
+```
+
+## `DockerServersService`
+
+The docker servers service is a core service that is always available in functional test runner tests. When you call `getService('dockerServers')` you will receive an instance of the `DockerServersService` class which has to methods:
+
+### `has(name: string): boolean`
+
+Determine if a name resolves to a known docker server.
+
+### `isEnabled(name: string): boolean`
+
+Determine if a named server is enabled.
+
+### `get(name: string): DockerServer`
+
+Get a `DockerServer` object for the server with the given name.
+
+
+## `DockerServer`
+
+The object passed to the `waitFor()` config function and returned by `DockerServersService#get()`
+
+```ts
+{
+ url: string;
+ name: string;
+
+ portInContainer: number;
+ port: number;
+ image: string;
+ waitForLogLine?: RegExp | string;
+ waitFor?: (server: DockerServer, logLine$: Rx.Observable) => Promise;
+}
+```
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_logs.ts b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_logs.ts
new file mode 100644
index 000000000000..f8e8137ce40a
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_logs.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 execa from 'execa';
+import * as Rx from 'rxjs';
+import { tap } from 'rxjs/operators';
+import { ToolingLog, observeLines } from '@kbn/dev-utils';
+
+/**
+ * Observe the logs for a container, reflecting the log lines
+ * to the ToolingLog and the returned Observable
+ */
+export function observeContainerLogs(name: string, containerId: string, log: ToolingLog) {
+ log.debug(`[docker:${name}] streaming logs from container [id=${containerId}]`);
+ const logsProc = execa('docker', ['logs', '--follow', containerId], {
+ stdio: ['ignore', 'pipe', 'pipe'],
+ });
+
+ const logLine$ = new Rx.Subject();
+
+ Rx.merge(
+ observeLines(logsProc.stdout).pipe(tap((line) => log.info(`[docker:${name}] ${line}`))),
+ observeLines(logsProc.stderr).pipe(tap((line) => log.error(`[docker:${name}] ${line}`)))
+ ).subscribe(logLine$);
+
+ return logLine$.asObservable();
+}
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_running.ts b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_running.ts
new file mode 100644
index 000000000000..3e3247a6ae3b
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/container_running.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 execa from 'execa';
+import * as Rx from 'rxjs';
+import { ToolingLog } from '@kbn/dev-utils';
+
+/**
+ * Create an observable that errors if a docker
+ * container exits before being unsubscribed
+ */
+export function observeContainerRunning(name: string, containerId: string, log: ToolingLog) {
+ return new Rx.Observable((subscriber) => {
+ log.debug(`[docker:${name}] watching container for exit status [${containerId}]`);
+
+ const exitCodeProc = execa('docker', ['wait', containerId]);
+
+ let exitCode: Error | number | null = null;
+ exitCodeProc
+ .then(({ stdout }) => {
+ exitCode = Number.parseInt(stdout.trim(), 10);
+
+ if (Number.isFinite(exitCode)) {
+ subscriber.error(
+ new Error(`container [id=${containerId}] unexpectedly exitted with code ${exitCode}`)
+ );
+ } else {
+ subscriber.error(
+ new Error(`unable to parse exit code from output of "docker wait": ${stdout}`)
+ );
+ }
+ })
+ .catch((error) => {
+ if (error?.killed) {
+ // ignore errors thrown because the process was killed
+ subscriber.complete();
+ return;
+ }
+
+ subscriber.error(
+ new Error(`failed to monitor process with "docker wait": ${error.message}`)
+ );
+ });
+
+ return () => {
+ exitCodeProc.kill('SIGKILL');
+ };
+ });
+}
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/define_docker_servers_config.ts b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/define_docker_servers_config.ts
new file mode 100644
index 000000000000..bb91ddbacf03
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/define_docker_servers_config.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 * as Rx from 'rxjs';
+
+export interface DockerServerSpec {
+ enabled: boolean;
+ portInContainer: number;
+ port: number;
+ image: string;
+ waitForLogLine?: RegExp | string;
+ /** a function that should return an observable that will allow the tests to execute as soon as it emits anything */
+ waitFor?: (server: DockerServer, logLine$: Rx.Observable) => Rx.Observable;
+ /* additional command line arguments passed to docker run */
+ args?: string[];
+}
+
+export interface DockerServer extends DockerServerSpec {
+ name: string;
+ url: string;
+}
+
+/**
+ * Helper that helps authors use the type definitions for the section of the FTR config
+ * under the `dockerServers` key.
+ */
+export function defineDockerServersConfig(config: { [name: string]: DockerServerSpec } | {}) {
+ return config;
+}
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/docker_servers_service.ts b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/docker_servers_service.ts
new file mode 100644
index 000000000000..606902228e1b
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/docker_servers_service.ts
@@ -0,0 +1,224 @@
+/*
+ * 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 Url from 'url';
+import execa from 'execa';
+import * as Rx from 'rxjs';
+import { filter, take, map } from 'rxjs/operators';
+import { ToolingLog } from '@kbn/dev-utils';
+
+import { Lifecycle } from '../lifecycle';
+import { observeContainerRunning } from './container_running';
+import { observeContainerLogs } from './container_logs';
+import { DockerServer, DockerServerSpec } from './define_docker_servers_config';
+
+const SECOND = 1000;
+
+export class DockerServersService {
+ private servers: DockerServer[];
+
+ constructor(
+ configs: {
+ [name: string]: DockerServerSpec;
+ },
+ private log: ToolingLog,
+ private lifecycle: Lifecycle
+ ) {
+ this.servers = Object.entries(configs).map(([name, config]) => ({
+ ...config,
+ name,
+ url: Url.format({
+ protocol: 'http:',
+ hostname: 'localhost',
+ port: config.port,
+ }),
+ }));
+
+ this.lifecycle.beforeTests.add(async () => {
+ await this.startServers();
+ });
+ }
+
+ isEnabled(name: string) {
+ return this.get(name).enabled;
+ }
+
+ has(name: string) {
+ return this.servers.some((s) => s.name === name);
+ }
+
+ get(name: string) {
+ const server = this.servers.find((s) => s.name === name);
+ if (!server) {
+ throw new Error(`no server with name "${name}"`);
+ }
+ return { ...server };
+ }
+
+ private async dockerRun(server: DockerServer) {
+ const { args } = server;
+ try {
+ this.log.info(`[docker:${server.name}] running image "${server.image}"`);
+
+ const dockerArgs = [
+ 'run',
+ '-dit',
+ args || [],
+ '-p',
+ `${server.port}:${server.portInContainer}`,
+ server.image,
+ ].flat();
+ const res = await execa('docker', dockerArgs);
+
+ return res.stdout.trim();
+ } catch (error) {
+ if (error?.exitCode === 125 && error?.message.includes('port is already allocated')) {
+ throw new Error(`
+ [docker:${server.name}] Another process is already listening on port ${server.port}.
+
+ When this happens on CI it is usually because the port number isn't taking into
+ account parallel workers running on the same machine.
+
+ When this happens locally it is usually because the functional test runner didn't
+ have a chance to cleanup the running docker containers before being killed.
+
+ To see if this is the case:
+
+ 1. make sure that there aren't other instances of the functional test runner running
+ 2. run \`docker ps\` to see the containers running
+ 3. if one of them lists that it is using port ${server.port} then kill it with \`docker kill "container ID"\`
+ `);
+ }
+
+ throw error;
+ }
+ }
+
+ private async startServer(server: DockerServer) {
+ const { log, lifecycle } = this;
+ const { image, name, waitFor, waitForLogLine } = server;
+
+ // pull image from registry
+ log.info(`[docker:${name}] pulling docker image "${image}"`);
+ await execa('docker', ['pull', image]);
+
+ // run the image that we just pulled
+ const containerId = await this.dockerRun(server);
+
+ lifecycle.cleanup.add(() => {
+ try {
+ execa.sync('docker', ['kill', containerId]);
+ execa.sync('docker', ['rm', containerId]);
+ } catch (error) {
+ if (
+ error.message.includes(`Container ${containerId} is not running`) ||
+ error.message.includes(`No such container: ${containerId}`)
+ ) {
+ return;
+ }
+
+ throw error;
+ }
+ });
+
+ // push the logs from the container to our logger, and expose an observable of those lines for testing
+ const logLine$ = observeContainerLogs(name, containerId, log);
+ lifecycle.cleanup.add(async () => {
+ await logLine$.toPromise();
+ });
+
+ // ensure container stays running, error if it exits
+ lifecycle.cleanup.addSub(
+ observeContainerRunning(name, containerId, log).subscribe({
+ error: (error) => {
+ lifecycle.cleanup.after$.subscribe(() => {
+ log.error(`[docker:${name}] Error ensuring that the container is running`);
+ log.error(error);
+ process.exit(1);
+ });
+
+ if (!lifecycle.cleanup.triggered) {
+ lifecycle.cleanup.trigger();
+ }
+ },
+ })
+ );
+
+ // wait for conditions before completing
+ if (waitForLogLine instanceof RegExp) {
+ log.info(`[docker:${name}] Waiting for log line matching /${waitForLogLine.source}/`);
+ }
+ if (typeof waitForLogLine === 'string') {
+ log.info(`[docker:${name}] Waiting for log line containing "${waitForLogLine}"`);
+ }
+ if (waitFor !== undefined) {
+ log.info(`[docker:${name}] Waiting for waitFor() promise to resolve`);
+ }
+ if (waitForLogLine === undefined && waitFor === undefined) {
+ log.warning(`
+ [docker:${name}] No "waitFor*" condition defined, you should always
+ define a wait condition to avoid race conditions that are more likely
+ to fail on CI because we're not waiting for the contained server to be ready.
+ `);
+ }
+
+ function firstWithTimeout(source$: Rx.Observable, errorMsg: string, ms = 30 * SECOND) {
+ return Rx.race(
+ source$.pipe(take(1)),
+ Rx.timer(ms).pipe(
+ map(() => {
+ throw new Error(`[docker:${name}] ${errorMsg} within ${ms / SECOND} seconds`);
+ })
+ )
+ );
+ }
+
+ await Rx.merge(
+ waitFor === undefined
+ ? Rx.EMPTY
+ : firstWithTimeout(
+ waitFor(server, logLine$),
+ `didn't find a line containing "${waitForLogLine}"`
+ ),
+
+ waitForLogLine === undefined
+ ? Rx.EMPTY
+ : firstWithTimeout(
+ logLine$.pipe(
+ filter((line) =>
+ waitForLogLine instanceof RegExp
+ ? waitForLogLine.test(line)
+ : line.includes(waitForLogLine)
+ )
+ ),
+ `waitForLogLine didn't emit anything`
+ )
+ ).toPromise();
+ }
+
+ private async startServers() {
+ await Promise.all(
+ this.servers.map(async (server) => {
+ if (server.enabled) {
+ await this.startServer(server);
+ }
+ })
+ );
+ }
+}
diff --git a/packages/kbn-test/src/functional_test_runner/lib/docker_servers/index.ts b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/index.ts
new file mode 100644
index 000000000000..23df9df0458d
--- /dev/null
+++ b/packages/kbn-test/src/functional_test_runner/lib/docker_servers/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+export * from './docker_servers_service';
+export * from './define_docker_servers_config';
diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts
index 2e534974e1d7..4c34f334a8c2 100644
--- a/packages/kbn-test/src/functional_test_runner/lib/index.ts
+++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts
@@ -23,4 +23,5 @@ export { readConfigFile, Config } from './config';
export { readProviderSpec, ProviderCollection, Provider } from './providers';
export { runTests, setupMocha } from './mocha';
export { FailureMetadata } from './failure_metadata';
+export * from './docker_servers';
export { SuiteTracker } from './suite_tracker';
diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts
index 5c7fdb532faa..70fcd4d217be 100644
--- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts
+++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts
@@ -28,6 +28,8 @@ export type GetArgsType> = T extends LifecyclePhas
export class LifecyclePhase {
private readonly handlers: Array<(...args: Args) => Promise | void> = [];
+ public triggered = false;
+
private readonly beforeSubj = new Rx.Subject();
public readonly before$ = this.beforeSubj.asObservable();
@@ -46,29 +48,39 @@ export class LifecyclePhase {
this.handlers.push(fn);
}
+ public addSub(sub: Rx.Subscription) {
+ this.handlers.push(() => {
+ sub.unsubscribe();
+ });
+ }
+
public async trigger(...args: Args) {
- if (this.beforeSubj.isStopped) {
+ if (this.options.singular && this.triggered) {
throw new Error(`singular lifecycle event can only be triggered once`);
}
+ this.triggered = true;
+
this.beforeSubj.next(undefined);
if (this.options.singular) {
this.beforeSubj.complete();
}
// catch the first error but still execute all handlers
- let error;
+ let error: Error | undefined;
// shuffle the handlers to prevent relying on their order
- for (const fn of shuffle(this.handlers)) {
- try {
- await fn(...args);
- } catch (_error) {
- if (!error) {
- error = _error;
+ await Promise.all(
+ shuffle(this.handlers).map(async (fn) => {
+ try {
+ await fn(...args);
+ } catch (_error) {
+ if (!error) {
+ error = _error;
+ }
}
- }
- }
+ })
+ );
this.afterSubj.next(undefined);
if (this.options.singular) {
diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts
index e6b180d979f1..bc17b731f0aa 100644
--- a/packages/kbn-test/src/index.ts
+++ b/packages/kbn-test/src/index.ts
@@ -52,4 +52,5 @@ export { makeJunitReportPath } from './junit_report_path';
export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix';
+export * from './functional_test_runner';
export * from './page_load_metrics';
diff --git a/packages/kbn-test/types/ftr.d.ts b/packages/kbn-test/types/ftr.d.ts
index 8beecab88878..38eb69d3e681 100644
--- a/packages/kbn-test/types/ftr.d.ts
+++ b/packages/kbn-test/types/ftr.d.ts
@@ -18,7 +18,12 @@
*/
import { ToolingLog } from '@kbn/dev-utils';
-import { Config, Lifecycle, FailureMetadata } from '../src/functional_test_runner/lib';
+import {
+ Config,
+ Lifecycle,
+ FailureMetadata,
+ DockerServersService,
+} from '../src/functional_test_runner/lib';
export { Lifecycle, Config, FailureMetadata };
@@ -61,7 +66,9 @@ export interface GenericFtrProviderContext<
* Determine if a service is avaliable
* @param serviceName
*/
- hasService(serviceName: 'config' | 'log' | 'lifecycle' | 'failureMetadata'): true;
+ hasService(
+ serviceName: 'config' | 'log' | 'lifecycle' | 'failureMetadata' | 'dockerServers'
+ ): true;
hasService(serviceName: K): serviceName is K;
hasService(serviceName: string): serviceName is Extract;
@@ -73,6 +80,7 @@ export interface GenericFtrProviderContext<
getService(serviceName: 'config'): Config;
getService(serviceName: 'log'): ToolingLog;
getService(serviceName: 'lifecycle'): Lifecycle;
+ getService(serviceName: 'dockerServers'): DockerServersService;
getService(serviceName: 'failureMetadata'): FailureMetadata;
getService(serviceName: T): ServiceMap[T];
diff --git a/test/functional/config.js b/test/functional/config.js
index c377e64f2fc4..95e0c689089e 100644
--- a/test/functional/config.js
+++ b/test/functional/config.js
@@ -40,6 +40,7 @@ export default async function ({ readConfigFile }) {
],
pageObjects,
services,
+
servers: commonConfig.get('servers'),
esTestCluster: commonConfig.get('esTestCluster'),
diff --git a/vars/workers.groovy b/vars/workers.groovy
index 8034a548285a..e44df9ef75ad 100644
--- a/vars/workers.groovy
+++ b/vars/workers.groovy
@@ -6,15 +6,15 @@ def label(size) {
case 'flyweight':
return 'flyweight'
case 's':
- return 'linux && immutable'
+ return 'docker && linux && immutable'
case 's-highmem':
- return 'tests-s'
+ return 'docker && tests-s'
case 'l':
- return 'tests-l'
+ return 'docker && tests-l'
case 'xl':
- return 'tests-xl'
+ return 'docker && tests-xl'
case 'xxl':
- return 'tests-xxl'
+ return 'docker && tests-xxl'
}
error "unknown size '${size}'"
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 8e261dde39da..94c90143b577 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-/* eslint-disable import/no-default-export */
-
import { resolve } from 'path';
import { services } from './services';
From 00e12fdc581aa6baca414d7f58e01bcb2e563481 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Sun, 21 Jun 2020 16:02:08 +0200
Subject: [PATCH 28/33] [APM] Add support for dark mode (#69362) (#69622)
---
.../plugins/apm/public/application/index.tsx | 15 +-
.../app/ErrorGroupDetails/index.tsx | 3 +-
.../components/app/ServiceMap/BetaBadge.tsx | 5 +-
.../components/app/ServiceMap/Controls.tsx | 33 +-
.../components/app/ServiceMap/Cytoscape.tsx | 32 +-
.../app/ServiceMap/EmptyBanner.test.tsx | 11 +-
.../components/app/ServiceMap/EmptyBanner.tsx | 15 +-
.../app/ServiceMap/LoadingOverlay.tsx | 3 +-
.../app/ServiceMap/Popover/Info.tsx | 3 +-
.../ServiceMap/Popover/ServiceMetricList.tsx | 3 +-
.../ServiceMap/Popover/anomaly_detection.tsx | 9 +-
.../app/ServiceMap/Popover/index.tsx | 20 +-
.../app/ServiceMap/cytoscapeOptions.ts | 328 +++++++++---------
.../components/app/ServiceMap/index.tsx | 16 +-
.../AgentConfigurations/List/index.tsx | 19 +-
.../Waterfall/SpanFlyout/DatabaseContext.tsx | 5 +-
.../Waterfall/SpanFlyout/HttpContext.tsx | 5 +-
.../Waterfall/WaterfallItem.tsx | 9 +-
.../public/components/shared/EuiTabLink.tsx | 5 +-
.../shared/KeyValueTable/FormattedValue.tsx | 3 +-
.../__test__/KeyValueTable.test.tsx | 3 +-
.../shared/KueryBar/Typeahead/Suggestion.js | 35 +-
.../shared/KueryBar/Typeahead/Suggestions.js | 7 +-
.../shared/LocalUIFilters/Filter/index.tsx | 7 +-
.../Stacktrace/CauseStacktrace.test.tsx | 7 +-
.../shared/Stacktrace/CauseStacktrace.tsx | 11 +-
.../components/shared/Stacktrace/Context.tsx | 21 +-
.../shared/Stacktrace/FrameHeading.tsx | 7 +-
.../shared/Stacktrace/Stackframe.tsx | 11 +-
.../shared/Stacktrace/Variables.tsx | 3 +-
.../Stacktrace/__test__/Stackframe.test.tsx | 11 +-
.../__snapshots__/Stackframe.test.tsx.snap | 60 ++--
.../shared/StickyProperties/index.tsx | 3 +-
.../Summary/ErrorCountSummaryItemBadge.tsx | 24 +-
.../Summary/UserAgentSummaryItem.test.tsx | 6 +-
.../shared/Summary/UserAgentSummaryItem.tsx | 3 +-
.../ErrorCountSummaryItemBadge.test.tsx | 11 +-
.../components/shared/Summary/index.tsx | 6 +-
.../charts/CustomPlot/AnnotationsPlot.tsx | 21 +-
.../shared/charts/CustomPlot/Legends.js | 17 +-
.../charts/CustomPlot/test/CustomPlot.test.js | 7 +-
.../Histogram/__test__/Histogram.test.js | 26 +-
.../components/shared/charts/Legend/index.tsx | 13 +-
.../Timeline/Marker/AgentMarker.test.tsx | 9 +-
.../charts/Timeline/Marker/AgentMarker.tsx | 12 +-
.../Timeline/Marker/ErrorMarker.test.tsx | 9 +-
.../charts/Timeline/Marker/ErrorMarker.tsx | 7 +-
.../__snapshots__/AgentMarker.test.tsx.snap | 34 +-
.../shared/charts/Timeline/Timeline.test.tsx | 11 +-
.../shared/charts/Timeline/TimelineAxis.tsx | 15 +-
.../shared/charts/Timeline/VerticalLines.tsx | 91 +++--
.../components/shared/charts/Tooltip/index.js | 19 +-
.../TransactionCharts/ChoroplethMap/index.tsx | 13 +-
x-pack/plugins/apm/public/hooks/useTheme.tsx | 14 +
.../plugins/apm/public/utils/testHelpers.tsx | 29 +-
55 files changed, 598 insertions(+), 527 deletions(-)
create mode 100644 x-pack/plugins/apm/public/hooks/useTheme.tsx
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 56c427e67ad4..e7191caced9d 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -8,8 +8,9 @@ import { ApmRoute } from '@elastic/apm-rum-react';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
-import styled from 'styled-components';
-import { EuiThemeProvider } from '../../../observability/public';
+import styled, { ThemeProvider, DefaultTheme } from 'styled-components';
+import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { CoreStart, AppMountParameters } from '../../../../../src/core/public';
import { ApmPluginSetupDeps } from '../plugin';
import { ApmPluginContext } from '../context/ApmPluginContext';
@@ -42,7 +43,13 @@ const App = () => {
const [darkMode] = useUiSetting$('theme:darkMode');
return (
-
+ ({
+ ...outerTheme,
+ eui: darkMode ? euiDarkVars : euiLightVars,
+ darkMode,
+ })}
+ >
@@ -54,7 +61,7 @@ const App = () => {
-
+
);
};
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
index d8885ec11c51..0ad8a52b740d 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
@@ -13,7 +13,6 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import React, { Fragment } from 'react';
import styled from 'styled-components';
@@ -34,7 +33,7 @@ const Titles = styled.div`
const Label = styled.div`
margin-bottom: ${px(units.quarter)};
font-size: ${fontSizes.small};
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
`;
const Message = styled.div`
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
index dfbdee647b50..b468470e3a17 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
@@ -5,15 +5,14 @@
*/
import { EuiBetaBadge } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import React from 'react';
import styled from 'styled-components';
const BetaBadgeContainer = styled.div`
- right: ${theme.gutterTypes.gutterMedium};
+ right: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
position: absolute;
- top: ${theme.gutterTypes.gutterSmall};
+ top: ${({ theme }) => theme.eui.gutterTypes.gutterSmall};
z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */
`;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
index 8775cebc0af5..bcc87cbf3581 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
@@ -4,41 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React, { useContext, useEffect, useState } from 'react';
import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import React, { useContext, useEffect, useState } from 'react';
import styled from 'styled-components';
import { CytoscapeContext } from './Cytoscape';
-import { animationOptions, nodeHeight } from './cytoscapeOptions';
+import { getAnimationOptions, getNodeHeight } from './cytoscapeOptions';
import { getAPMHref } from '../../shared/Links/apm/APMLink';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { APMQueryParams } from '../../shared/Links/url_helpers';
+import { useTheme } from '../../../hooks/useTheme';
const ControlsContainer = styled('div')`
- left: ${theme.gutterTypes.gutterMedium};
+ left: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
position: absolute;
- top: ${theme.gutterTypes.gutterSmall};
+ top: ${({ theme }) => theme.eui.gutterTypes.gutterSmall};
z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */
`;
const Button = styled(EuiButtonIcon)`
display: block;
- margin: ${theme.paddingSizes.xs};
+ margin: ${({ theme }) => theme.eui.paddingSizes.xs};
`;
const ZoomInButton = styled(Button)`
- margin-bottom: ${theme.paddingSizes.s};
+ margin-bottom: ${({ theme }) => theme.eui.paddingSizes.s};
`;
const Panel = styled(EuiPanel)`
- margin-bottom: ${theme.paddingSizes.s};
+ margin-bottom: ${({ theme }) => theme.eui.paddingSizes.s};
`;
-const duration = parseInt(theme.euiAnimSpeedFast, 10);
const steps = 5;
-function doZoom(cy: cytoscape.Core | undefined, increment: number) {
+function doZoom(
+ cy: cytoscape.Core | undefined,
+ increment: number,
+ duration: number
+) {
if (cy) {
const level = cy.zoom() + increment;
// @ts-ignore `.position()` _does_ work on a NodeCollection. It returns the position of the first element in the collection.
@@ -93,10 +96,12 @@ function useDebugDownloadUrl(cy?: cytoscape.Core) {
}
export function Controls() {
+ const theme = useTheme();
const cy = useContext(CytoscapeContext);
const { urlParams } = useUrlParams();
const currentSearch = urlParams.kuery ?? '';
const [zoom, setZoom] = useState((cy && cy.zoom()) || 1);
+ const duration = parseInt(theme.eui.euiAnimSpeedFast, 10);
const downloadUrl = useDebugDownloadUrl(cy);
// Handle zoom events
@@ -120,19 +125,19 @@ export function Controls() {
if (cy) {
const eles = cy.nodes();
cy.animate({
- ...animationOptions,
+ ...getAnimationOptions(theme),
center: { eles },
- fit: { eles, padding: nodeHeight },
+ fit: { eles, padding: getNodeHeight(theme) },
});
}
}
function zoomIn() {
- doZoom(cy, increment);
+ doZoom(cy, increment, duration);
}
function zoomOut() {
- doZoom(cy, -increment);
+ doZoom(cy, -increment, duration);
}
if (!cy) {
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
index c2764d3adfa7..1cde473aae6f 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import cytoscape from 'cytoscape';
import React, {
createContext,
CSSProperties,
@@ -13,11 +12,13 @@ import React, {
useRef,
useState,
} from 'react';
+import cytoscape from 'cytoscape';
import { debounce } from 'lodash';
+import { useTheme } from '../../../hooks/useTheme';
import {
- animationOptions,
- cytoscapeOptions,
- nodeHeight,
+ getAnimationOptions,
+ getCytoscapeOptions,
+ getNodeHeight,
} from './cytoscapeOptions';
import { useUiTracker } from '../../../../../observability/public';
@@ -73,7 +74,8 @@ function rotatePoint(
function getLayoutOptions(
selectedRoots: string[],
height: number,
- width: number
+ width: number,
+ nodeHeight: number
): cytoscape.LayoutOptions {
return {
name: 'breadthfirst',
@@ -111,11 +113,14 @@ export function Cytoscape({
serviceName,
style,
}: CytoscapeProps) {
+ const theme = useTheme();
const [ref, cy] = useCytoscape({
- ...cytoscapeOptions,
+ ...getCytoscapeOptions(theme),
elements,
});
+ const nodeHeight = getNodeHeight(theme);
+
// Add the height to the div style. The height is a separate prop because it
// is required and can trigger rendering when changed.
const divStyle = { ...style, height };
@@ -149,7 +154,7 @@ export function Cytoscape({
const selectedRoots = selectRoots(event.cy);
const layout = cy.layout(
- getLayoutOptions(selectedRoots, height, width)
+ getLayoutOptions(selectedRoots, height, width, nodeHeight)
);
layout.run();
@@ -162,7 +167,7 @@ export function Cytoscape({
layoutstopDelayTimeout = setTimeout(() => {
if (serviceName) {
event.cy.animate({
- ...animationOptions,
+ ...getAnimationOptions(theme),
fit: {
eles: event.cy.elements(),
padding: nodeHeight,
@@ -237,7 +242,16 @@ export function Cytoscape({
}
clearTimeout(layoutstopDelayTimeout);
};
- }, [cy, elements, height, serviceName, trackApmEvent, width]);
+ }, [
+ cy,
+ elements,
+ height,
+ serviceName,
+ trackApmEvent,
+ width,
+ nodeHeight,
+ theme,
+ ]);
return (
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.test.tsx
index d497711cd0c2..b330129f8378 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.test.tsx
@@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { act, render, wait } from '@testing-library/react';
-import cytoscape from 'cytoscape';
import React, { FunctionComponent } from 'react';
+import { act, wait } from '@testing-library/react';
+import cytoscape from 'cytoscape';
import { CytoscapeContext } from './Cytoscape';
import { EmptyBanner } from './EmptyBanner';
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';
+import { renderWithTheme } from '../../../utils/testHelpers';
const cy = cytoscape({});
@@ -29,7 +30,7 @@ describe('EmptyBanner', () => {
);
- const component = render( , {
+ const component = renderWithTheme( , {
wrapper: noCytoscapeWrapper,
});
@@ -39,7 +40,7 @@ describe('EmptyBanner', () => {
describe('with no nodes', () => {
it('renders null', () => {
- const component = render( , {
+ const component = renderWithTheme( , {
wrapper,
});
@@ -49,7 +50,7 @@ describe('EmptyBanner', () => {
describe('with one node', () => {
it('does not render null', async () => {
- const component = render( , { wrapper });
+ const component = renderWithTheme( , { wrapper });
await act(async () => {
cy.add({ data: { id: 'test id' } });
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx
index 86582d57b3d7..63a9cf985959 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx
@@ -4,26 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React, { useContext, useEffect, useState } from 'react';
import { EuiCallOut } from '@elastic/eui';
-import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import React, { useContext, useEffect, useState } from 'react';
import styled from 'styled-components';
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
import { CytoscapeContext } from './Cytoscape';
+import { useTheme } from '../../../hooks/useTheme';
const EmptyBannerContainer = styled.div`
- margin: ${lightTheme.gutterTypes.gutterSmall};
+ margin: ${({ theme }) => theme.eui.gutterTypes.gutterSmall};
/* Add some extra margin so it displays to the right of the controls. */
left: calc(
- ${lightTheme.gutterTypes.gutterExtraLarge} +
- ${lightTheme.gutterTypes.gutterSmall}
+ ${({ theme }) => theme.eui.gutterTypes.gutterExtraLarge} +
+ ${({ theme }) => theme.eui.gutterTypes.gutterSmall}
);
position: absolute;
z-index: 1;
`;
export function EmptyBanner() {
+ const theme = useTheme();
const cy = useContext(CytoscapeContext);
const [nodeCount, setNodeCount] = useState(0);
@@ -51,8 +52,8 @@ export function EmptyBanner() {
// subtract the space for controls and margins.
const width =
cy.width() -
- parseInt(lightTheme.gutterTypes.gutterExtraLarge, 10) -
- parseInt(lightTheme.gutterTypes.gutterLarge, 10);
+ parseInt(theme.eui.gutterTypes.gutterExtraLarge, 10) -
+ parseInt(theme.eui.gutterTypes.gutterLarge, 10);
return (
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx
index 23fd568648ba..9e805058e8cb 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
import { EuiProgress, EuiText, EuiSpacer } from '@elastic/eui';
import styled from 'styled-components';
@@ -22,7 +21,7 @@ const Overlay = styled.div`
flex-direction: column;
align-items: center;
width: 100%;
- padding: ${theme.gutterTypes.gutterMedium};
+ padding: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
`;
const ProgressBarContainer = styled.div`
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx
index 992e6ffdc46b..223d342e6799 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import cytoscape from 'cytoscape';
import React from 'react';
@@ -19,7 +18,7 @@ const ItemRow = styled.div`
`;
const ItemTitle = styled.dt`
- color: ${lightTheme.textColors.subdued};
+ color: ${({ theme }) => theme.eui.textColors.subdued};
`;
const ItemDescription = styled.dd``;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
index 4e6f077a80ae..d66be9c61e42 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
@@ -5,7 +5,6 @@
*/
import { EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui';
-import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { isNumber } from 'lodash';
import React from 'react';
@@ -30,7 +29,7 @@ export const ItemRow = styled('tr')`
`;
export const ItemTitle = styled('td')`
- color: ${lightTheme.textColors.subdued};
+ color: ${({ theme }) => theme.eui.textColors.subdued};
padding-right: 1rem;
`;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
index ad4dc2ced2bf..531bbb139d58 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
@@ -7,7 +7,6 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import styled from 'styled-components';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import {
EuiFlexGroup,
EuiFlexItem,
@@ -15,6 +14,7 @@ import {
EuiIconTip,
EuiHealth,
} from '@elastic/eui';
+import { useTheme } from '../../../../hooks/useTheme';
import { fontSize, px } from '../../../../style/variables';
import { asInteger } from '../../../../utils/formatters';
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
@@ -33,11 +33,11 @@ const VerticallyCentered = styled.div`
`;
const SubduedText = styled.span`
- color: ${theme.euiTextSubduedColor};
+ color: ${({ theme }) => theme.eui.euiTextSubduedColor};
`;
const EnableText = styled.section`
- color: ${theme.euiTextSubduedColor};
+ color: ${({ theme }) => theme.eui.euiTextSubduedColor};
line-height: 1.4;
font-size: ${fontSize};
width: ${px(popoverMinWidth)};
@@ -52,6 +52,7 @@ interface AnomalyDetectionProps {
}
export function AnomalyDetection({ serviceNodeData }: AnomalyDetectionProps) {
+ const theme = useTheme();
const anomalySeverity = serviceNodeData.anomaly_severity;
const anomalyScore = serviceNodeData.anomaly_score;
const actualValue = serviceNodeData.actual_value;
@@ -81,7 +82,7 @@ export function AnomalyDetection({ serviceNodeData }: AnomalyDetectionProps) {
-
+
{ANOMALY_DETECTION_SCORE_METRIC}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
index 781f762d5854..e8b6b87b7b4d 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
@@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiPopover } from '@elastic/eui';
-import cytoscape from 'cytoscape';
import React, {
CSSProperties,
MouseEvent,
@@ -15,9 +13,12 @@ import React, {
useRef,
useState,
} from 'react';
+import { EuiPopover } from '@elastic/eui';
+import cytoscape from 'cytoscape';
+import { useTheme } from '../../../../hooks/useTheme';
import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames';
import { CytoscapeContext } from '../Cytoscape';
-import { animationOptions } from '../cytoscapeOptions';
+import { getAnimationOptions } from '../cytoscapeOptions';
import { Contents } from './Contents';
interface PopoverProps {
@@ -25,6 +26,7 @@ interface PopoverProps {
}
export function Popover({ focusedServiceName }: PopoverProps) {
+ const theme = useTheme();
const cy = useContext(CytoscapeContext);
const [selectedNode, setSelectedNode] = useState<
cytoscape.NodeSingular | undefined
@@ -94,16 +96,20 @@ export function Popover({ focusedServiceName }: PopoverProps) {
event.preventDefault();
if (cy) {
cy.animate({
- ...animationOptions,
+ ...getAnimationOptions(theme),
center: { eles: cy.getElementById(selectedNodeServiceName) },
});
}
},
- [cy, selectedNodeServiceName]
+ [cy, selectedNodeServiceName, theme]
);
const isAlreadyFocused = focusedServiceName === selectedNodeServiceName;
+ const onFocusClick = isAlreadyFocused
+ ? centerSelectedNode
+ : (_event: MouseEvent) => deselect();
+
return (
deselect()
- }
+ onFocusClick={onFocusClick}
selectedNodeData={selectedNodeData}
selectedNodeServiceName={selectedNodeServiceName}
/>
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
index 5107d36df85d..5a2a3d2a2644 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
@@ -3,46 +3,49 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import cytoscape from 'cytoscape';
import { CSSProperties } from 'react';
import {
SERVICE_NAME,
SPAN_DESTINATION_SERVICE_RESOURCE,
} from '../../../../common/elasticsearch_fieldnames';
+import { EuiTheme } from '../../../../../observability/public';
import { severity } from '../../../../common/ml_job_constants';
import { defaultIcon, iconForNode } from './icons';
export const popoverMinWidth = 280;
-export const getSeverityColor = (nodeSeverity?: string) => {
+export function getSeverityColor(theme: EuiTheme, nodeSeverity?: string) {
switch (nodeSeverity) {
case severity.warning:
- return theme.euiColorVis0;
+ return theme.eui.euiColorVis0;
case severity.minor:
case severity.major:
- return theme.euiColorVis5;
+ return theme.eui.euiColorVis5;
case severity.critical:
- return theme.euiColorVis9;
+ return theme.eui.euiColorVis9;
default:
return;
}
-};
+}
-const getBorderColor: cytoscape.Css.MapperFunction<
- cytoscape.NodeSingular,
- string
-> = (el: cytoscape.NodeSingular) => {
- const hasAnomalyDetectionJob = el.data('ml_job_id') !== undefined;
- const nodeSeverity = el.data('anomaly_severity');
- if (hasAnomalyDetectionJob) {
- return getSeverityColor(nodeSeverity) || theme.euiColorMediumShade;
- }
- if (el.hasClass('primary') || el.selected()) {
- return theme.euiColorPrimary;
- }
- return theme.euiColorMediumShade;
-};
+function getBorderColorFn(
+ theme: EuiTheme
+): cytoscape.Css.MapperFunction {
+ return (el: cytoscape.NodeSingular) => {
+ const hasAnomalyDetectionJob = el.data('ml_job_id') !== undefined;
+ const nodeSeverity = el.data('anomaly_severity');
+ if (hasAnomalyDetectionJob) {
+ return (
+ getSeverityColor(theme, nodeSeverity) || theme.eui.euiColorMediumShade
+ );
+ }
+ if (el.hasClass('primary') || el.selected()) {
+ return theme.eui.euiColorPrimary;
+ }
+ return theme.eui.euiColorMediumShade;
+ };
+}
const getBorderStyle: cytoscape.Css.MapperFunction<
cytoscape.NodeSingular,
@@ -56,7 +59,7 @@ const getBorderStyle: cytoscape.Css.MapperFunction<
}
};
-const getBorderWidth = (el: cytoscape.NodeSingular) => {
+function getBorderWidth(el: cytoscape.NodeSingular) {
const nodeSeverity = el.data('anomaly_severity');
if (nodeSeverity === severity.minor || nodeSeverity === severity.major) {
@@ -66,7 +69,7 @@ const getBorderWidth = (el: cytoscape.NodeSingular) => {
} else {
return 4;
}
-};
+}
// IE 11 does not properly load some SVGs or draw certain shapes. This causes
// a runtime error and the map fails work at all. We would prefer to do some
@@ -79,172 +82,183 @@ const getBorderWidth = (el: cytoscape.NodeSingular) => {
// @ts-ignore `documentMode` is not recognized as a valid property of `document`.
const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
-export const animationOptions: cytoscape.AnimationOptions = {
- duration: parseInt(theme.euiAnimSpeedNormal, 10),
+export const getAnimationOptions = (
+ theme: EuiTheme
+): cytoscape.AnimationOptions => ({
+ duration: parseInt(theme.eui.euiAnimSpeedNormal, 10),
// @ts-ignore The cubic-bezier options here are not recognized by the cytoscape types
- easing: theme.euiAnimSlightBounce,
-};
-const lineColor = '#C5CCD7';
+ easing: theme.eui.euiAnimSlightBounce,
+});
+
const zIndexNode = 200;
const zIndexEdge = 100;
const zIndexEdgeHighlight = 110;
const zIndexEdgeHover = 120;
-export const nodeHeight = parseInt(theme.avatarSizing.l.size, 10);
+
+export const getNodeHeight = (theme: EuiTheme): number =>
+ parseInt(theme.eui.avatarSizing.l.size, 10);
function isService(el: cytoscape.NodeSingular) {
return el.data(SERVICE_NAME) !== undefined;
}
-const style: cytoscape.Stylesheet[] = [
- {
- selector: 'node',
- style: {
- 'background-color': 'white',
- // The DefinitelyTyped definitions don't specify that a function can be
- // used here.
- //
- // @ts-ignore
- 'background-image': isIE11
- ? undefined
- : (el: cytoscape.NodeSingular) => iconForNode(el) ?? defaultIcon,
- 'background-height': (el: cytoscape.NodeSingular) =>
- isService(el) ? '60%' : '40%',
- 'background-width': (el: cytoscape.NodeSingular) =>
- isService(el) ? '60%' : '40%',
- 'border-color': getBorderColor,
- 'border-style': getBorderStyle,
- 'border-width': getBorderWidth,
- color: (el: cytoscape.NodeSingular) =>
- el.hasClass('primary') || el.selected()
- ? theme.euiColorPrimaryText
- : theme.textColors.text,
- // theme.euiFontFamily doesn't work here for some reason, so we're just
- // specifying a subset of the fonts for the label text.
- 'font-family': 'Inter UI, Segoe UI, Helvetica, Arial, sans-serif',
- 'font-size': theme.euiFontSizeS,
- ghost: 'yes',
- 'ghost-offset-x': 0,
- 'ghost-offset-y': 2,
- 'ghost-opacity': 0.15,
- height: nodeHeight,
- label: (el: cytoscape.NodeSingular) =>
- isService(el)
- ? el.data(SERVICE_NAME)
- : el.data(SPAN_DESTINATION_SERVICE_RESOURCE),
- 'min-zoomed-font-size': parseInt(theme.euiSizeS, 10),
- 'overlay-opacity': 0,
- shape: (el: cytoscape.NodeSingular) =>
- isService(el) ? (isIE11 ? 'rectangle' : 'ellipse') : 'diamond',
- 'text-background-color': theme.euiColorPrimary,
- 'text-background-opacity': (el: cytoscape.NodeSingular) =>
- el.hasClass('primary') || el.selected() ? 0.1 : 0,
- 'text-background-padding': theme.paddingSizes.xs,
- 'text-background-shape': 'roundrectangle',
- 'text-margin-y': parseInt(theme.paddingSizes.s, 10),
- 'text-max-width': '200px',
- 'text-valign': 'bottom',
- 'text-wrap': 'ellipsis',
- width: theme.avatarSizing.l.size,
- 'z-index': zIndexNode,
+const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => {
+ const lineColor = theme.eui.euiColorMediumShade;
+ return [
+ {
+ selector: 'node',
+ style: {
+ 'background-color': theme.eui.euiColorGhost,
+ // The DefinitelyTyped definitions don't specify that a function can be
+ // used here.
+ //
+ // @ts-ignore
+ 'background-image': isIE11
+ ? undefined
+ : (el: cytoscape.NodeSingular) => iconForNode(el) ?? defaultIcon,
+ 'background-height': (el: cytoscape.NodeSingular) =>
+ isService(el) ? '60%' : '40%',
+ 'background-width': (el: cytoscape.NodeSingular) =>
+ isService(el) ? '60%' : '40%',
+ 'border-color': getBorderColorFn(theme),
+ 'border-style': getBorderStyle,
+ 'border-width': getBorderWidth,
+ color: (el: cytoscape.NodeSingular) =>
+ el.hasClass('primary') || el.selected()
+ ? theme.eui.euiColorPrimaryText
+ : theme.eui.textColors.text,
+ // theme.euiFontFamily doesn't work here for some reason, so we're just
+ // specifying a subset of the fonts for the label text.
+ 'font-family': 'Inter UI, Segoe UI, Helvetica, Arial, sans-serif',
+ 'font-size': theme.eui.euiFontSizeS,
+ ghost: 'yes',
+ 'ghost-offset-x': 0,
+ 'ghost-offset-y': 2,
+ 'ghost-opacity': 0.15,
+ height: getNodeHeight(theme),
+ label: (el: cytoscape.NodeSingular) =>
+ isService(el)
+ ? el.data(SERVICE_NAME)
+ : el.data(SPAN_DESTINATION_SERVICE_RESOURCE),
+ 'min-zoomed-font-size': parseInt(theme.eui.euiSizeS, 10),
+ 'overlay-opacity': 0,
+ shape: (el: cytoscape.NodeSingular) =>
+ isService(el) ? (isIE11 ? 'rectangle' : 'ellipse') : 'diamond',
+ 'text-background-color': theme.eui.euiColorPrimary,
+ 'text-background-opacity': (el: cytoscape.NodeSingular) =>
+ el.hasClass('primary') || el.selected() ? 0.1 : 0,
+ 'text-background-padding': theme.eui.paddingSizes.xs,
+ 'text-background-shape': 'roundrectangle',
+ 'text-margin-y': parseInt(theme.eui.paddingSizes.s, 10),
+ 'text-max-width': '200px',
+ 'text-valign': 'bottom',
+ 'text-wrap': 'ellipsis',
+ width: theme.eui.avatarSizing.l.size,
+ 'z-index': zIndexNode,
+ },
},
- },
- {
- selector: 'edge',
- style: {
- 'curve-style': 'taxi',
- // @ts-ignore
- 'taxi-direction': 'auto',
- 'line-color': lineColor,
- 'overlay-opacity': 0,
- 'target-arrow-color': lineColor,
- 'target-arrow-shape': isIE11 ? 'none' : 'triangle',
- // The DefinitelyTyped definitions don't specify this property since it's
- // fairly new.
- //
- // @ts-ignore
- 'target-distance-from-node': isIE11 ? undefined : theme.paddingSizes.xs,
- width: 1,
- 'source-arrow-shape': 'none',
- 'z-index': zIndexEdge,
+ {
+ selector: 'edge',
+ style: {
+ 'curve-style': 'taxi',
+ // @ts-ignore
+ 'taxi-direction': 'auto',
+ 'line-color': lineColor,
+ 'overlay-opacity': 0,
+ 'target-arrow-color': lineColor,
+ 'target-arrow-shape': isIE11 ? 'none' : 'triangle',
+ // The DefinitelyTyped definitions don't specify this property since it's
+ // fairly new.
+ //
+ // @ts-ignore
+ 'target-distance-from-node': isIE11
+ ? undefined
+ : theme.eui.paddingSizes.xs,
+ width: 1,
+ 'source-arrow-shape': 'none',
+ 'z-index': zIndexEdge,
+ },
},
- },
- {
- selector: 'edge[bidirectional]',
- style: {
- 'source-arrow-shape': isIE11 ? 'none' : 'triangle',
- 'source-arrow-color': lineColor,
- 'target-arrow-shape': isIE11 ? 'none' : 'triangle',
- // @ts-ignore
- 'source-distance-from-node': isIE11
- ? undefined
- : parseInt(theme.paddingSizes.xs, 10),
- 'target-distance-from-node': isIE11
- ? undefined
- : parseInt(theme.paddingSizes.xs, 10),
+ {
+ selector: 'edge[bidirectional]',
+ style: {
+ 'source-arrow-shape': isIE11 ? 'none' : 'triangle',
+ 'source-arrow-color': lineColor,
+ 'target-arrow-shape': isIE11 ? 'none' : 'triangle',
+ // @ts-ignore
+ 'source-distance-from-node': isIE11
+ ? undefined
+ : parseInt(theme.eui.paddingSizes.xs, 10),
+ 'target-distance-from-node': isIE11
+ ? undefined
+ : parseInt(theme.eui.paddingSizes.xs, 10),
+ },
},
- },
- {
- selector: 'edge[isInverseEdge]',
- // @ts-ignore DefinitelyTyped says visibility is "none" but it's
- // actually "hidden"
- style: { visibility: 'hidden' },
- },
- {
- selector: 'edge.nodeHover',
- style: {
- width: 2,
- // @ts-ignore
- 'z-index': zIndexEdgeHover,
- 'line-color': theme.euiColorDarkShade,
- 'source-arrow-color': theme.euiColorDarkShade,
- 'target-arrow-color': theme.euiColorDarkShade,
+ {
+ selector: 'edge[isInverseEdge]',
+ // @ts-ignore DefinitelyTyped says visibility is "none" but it's
+ // actually "hidden"
+ style: { visibility: 'hidden' },
},
- },
- {
- selector: 'node.hover',
- style: {
- 'border-width': getBorderWidth,
+ {
+ selector: 'edge.nodeHover',
+ style: {
+ width: 4,
+ // @ts-ignore
+ 'z-index': zIndexEdgeHover,
+ 'line-color': theme.eui.euiColorDarkShade,
+ 'source-arrow-color': theme.eui.euiColorDarkShade,
+ 'target-arrow-color': theme.eui.euiColorDarkShade,
+ },
},
- },
- {
- selector: 'edge.highlight',
- style: {
- width: 2,
- 'line-color': theme.euiColorPrimary,
- 'source-arrow-color': theme.euiColorPrimary,
- 'target-arrow-color': theme.euiColorPrimary,
- // @ts-ignore
- 'z-index': zIndexEdgeHighlight,
+ {
+ selector: 'node.hover',
+ style: {
+ 'border-width': getBorderWidth,
+ },
},
- },
-];
+ {
+ selector: 'edge.highlight',
+ style: {
+ width: 4,
+ 'line-color': theme.eui.euiColorPrimary,
+ 'source-arrow-color': theme.eui.euiColorPrimary,
+ 'target-arrow-color': theme.eui.euiColorPrimary,
+ // @ts-ignore
+ 'z-index': zIndexEdgeHighlight,
+ },
+ },
+ ];
+};
// The CSS styles for the div containing the cytoscape element. Makes a
// background grid of dots.
-export const cytoscapeDivStyle: CSSProperties = {
+export const getCytoscapeDivStyle = (theme: EuiTheme): CSSProperties => ({
background: `linear-gradient(
90deg,
- ${theme.euiPageBackgroundColor}
- calc(${theme.euiSizeL} - calc(${theme.euiSizeXS} / 2)),
+ ${theme.eui.euiPageBackgroundColor}
+ calc(${theme.eui.euiSizeL} - calc(${theme.eui.euiSizeXS} / 2)),
transparent 1%
)
center,
linear-gradient(
- ${theme.euiPageBackgroundColor}
- calc(${theme.euiSizeL} - calc(${theme.euiSizeXS} / 2)),
+ ${theme.eui.euiPageBackgroundColor}
+ calc(${theme.eui.euiSizeL} - calc(${theme.eui.euiSizeXS} / 2)),
transparent 1%
)
center,
-${theme.euiColorLightShade}`,
- backgroundSize: `${theme.euiSizeL} ${theme.euiSizeL}`,
- margin: `-${theme.gutterTypes.gutterLarge}`,
+${theme.eui.euiColorLightShade}`,
+ backgroundSize: `${theme.eui.euiSizeL} ${theme.eui.euiSizeL}`,
+ margin: `-${theme.eui.gutterTypes.gutterLarge}`,
marginTop: 0,
-};
+});
-export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
+export const getCytoscapeOptions = (
+ theme: EuiTheme
+): cytoscape.CytoscapeOptions => ({
autoungrabify: true,
boxSelectionEnabled: false,
maxZoom: 3,
minZoom: 0.2,
- style,
-};
+ style: getStyle(theme),
+});
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
index 74e95b7b92e8..7f3d25efa6f4 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
@@ -3,9 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
+import { useTheme } from '../../../hooks/useTheme';
import {
invalidLicenseMessage,
isValidPlatinumLicense,
@@ -17,7 +18,7 @@ import { callApmApi } from '../../../services/rest/createCallApmApi';
import { LicensePrompt } from '../../shared/LicensePrompt';
import { Controls } from './Controls';
import { Cytoscape } from './Cytoscape';
-import { cytoscapeDivStyle } from './cytoscapeOptions';
+import { getCytoscapeDivStyle } from './cytoscapeOptions';
import { EmptyBanner } from './EmptyBanner';
import { Popover } from './Popover';
import { useRefDimensions } from './useRefDimensions';
@@ -28,7 +29,8 @@ interface ServiceMapProps {
serviceName?: string;
}
-export function ServiceMap({ serviceName }: ServiceMapProps) {
+export const ServiceMap = ({ serviceName }: ServiceMapProps) => {
+ const theme = useTheme();
const license = useLicense();
const { urlParams } = useUrlParams();
@@ -66,14 +68,16 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
return isValidPlatinumLicense(license) ? (
@@ -97,4 +101,4 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
);
-}
+};
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
index 6d850b66f298..0f23e230733b 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
@@ -15,7 +15,7 @@ import {
EuiButtonIcon,
} from '@elastic/eui';
import { isEmpty } from 'lodash';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
+import { useTheme } from '../../../../../hooks/useTheme';
import { FETCH_STATUS } from '../../../../../hooks/useFetcher';
import { ITableColumn, ManagedTable } from '../../../../shared/ManagedTable';
import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt';
@@ -32,15 +32,14 @@ import { ConfirmDeleteModal } from './ConfirmDeleteModal';
type Config = AgentConfigurationListAPIResponse[0];
-export function AgentConfigurationList({
- status,
- data,
- refetch,
-}: {
+interface Props {
status: FETCH_STATUS;
data: Config[];
refetch: () => void;
-}) {
+}
+
+export const AgentConfigurationList = ({ status, data, refetch }: Props) => {
+ const theme = useTheme();
const [configToBeDeleted, setConfigToBeDeleted] = useState(
null
);
@@ -128,7 +127,9 @@ export function AgentConfigurationList({
)
}
>
-
+
),
},
@@ -218,4 +219,4 @@ export function AgentConfigurationList({
/>
>
);
-}
+};
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx
index 933cb3c8e863..7bceeb9ac165 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx
@@ -5,7 +5,6 @@
*/
import { EuiSpacer, EuiTitle } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { tint } from 'polished';
import React, { Fragment } from 'react';
@@ -33,9 +32,9 @@ registerLanguage('sql', sql);
const DatabaseStatement = styled.div`
padding: ${px(units.half)} ${px(unit)};
- background: ${tint(0.1, theme.euiColorWarning)};
+ background: ${({ theme }) => tint(0.1, theme.eui.euiColorWarning)};
border-radius: ${borderRadius};
- border: 1px solid ${theme.euiColorLightShade};
+ border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
font-family: ${fontFamilyCode};
font-size: ${fontSize};
`;
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx
index 7b296ebc54ea..cfe174168ca3 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx
@@ -8,7 +8,6 @@ import React, { Fragment } from 'react';
import styled from 'styled-components';
import { EuiSpacer, EuiTitle } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import {
borderRadius,
fontFamilyCode,
@@ -21,9 +20,9 @@ import { Span } from '../../../../../../../../typings/es_schemas/ui/span';
const ContextUrl = styled.div`
padding: ${px(units.half)} ${px(unit)};
- background: ${theme.euiColorLightestShade};
+ background: ${({ theme }) => theme.eui.euiColorLightestShade};
border-radius: ${borderRadius};
- border: 1px solid ${theme.euiColorLightShade};
+ border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
font-family: ${fontFamilyCode};
font-size: ${fontSize};
`;
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx
index 1226db7e36c5..a25ae71947f2 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx
@@ -8,7 +8,6 @@ import React from 'react';
import styled from 'styled-components';
import { EuiIcon, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { isRumAgentName } from '../../../../../../../common/agent_name';
import { px, unit, units } from '../../../../../../style/variables';
@@ -41,13 +40,13 @@ const Container = styled.div`
padding-bottom: ${px(units.plus)};
margin-right: ${(props) => px(props.timelineMargins.right)};
margin-left: ${(props) => px(props.timelineMargins.left)};
- border-top: 1px solid ${theme.euiColorLightShade};
- background-color: ${(props) =>
- props.isSelected ? theme.euiColorLightestShade : 'initial'};
+ border-top: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
+ background-color: ${({ isSelected, theme }) =>
+ isSelected ? theme.eui.euiColorLightestShade : 'initial'};
cursor: pointer;
&:hover {
- background-color: ${theme.euiColorLightestShade};
+ background-color: ${({ theme }) => theme.eui.euiColorLightestShade};
}
`;
diff --git a/x-pack/plugins/apm/public/components/shared/EuiTabLink.tsx b/x-pack/plugins/apm/public/components/shared/EuiTabLink.tsx
index 73fd2544a34a..8538ea6a510c 100644
--- a/x-pack/plugins/apm/public/components/shared/EuiTabLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/EuiTabLink.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import cls from 'classnames';
import styled from 'styled-components';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { px, unit } from '../../style/variables';
// TODO: replace this component with EUITab w/ a href prop
@@ -28,8 +27,8 @@ const Wrapper = styled.div<{ isSelected: boolean }>`
a {
display: inline-block;
padding: ${px(unit * 0.75)} ${px(unit)};
- ${({ isSelected }) =>
- !isSelected ? `color: ${theme.euiTextColor} !important;` : ''}
+ ${({ isSelected, theme }) =>
+ !isSelected ? `color: ${theme.eui.euiTextColor} !important;` : ''}
}
`;
diff --git a/x-pack/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx b/x-pack/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx
index ac4f8a2086bd..54c46c5559a0 100644
--- a/x-pack/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx
+++ b/x-pack/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx
@@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { isBoolean, isNumber, isObject } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
const EmptyValue = styled.span`
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
text-align: left;
`;
diff --git a/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx b/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx
index 4384b21c0615..5a9e8809ea73 100644
--- a/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { KeyValueTable } from '..';
import { render } from '@testing-library/react';
+import { renderWithTheme } from '../../../../utils/testHelpers';
function getKeys(output: ReturnType) {
const keys = output.getAllByTestId('dot-key');
@@ -31,7 +32,7 @@ describe('KeyValueTable', () => {
{ key: 'nested.b.c', value: 'ccc' },
{ key: 'nested.a', value: 'aaa' },
];
- const output = render( );
+ const output = renderWithTheme( );
const rows = output.container.querySelectorAll('tr');
expect(rows.length).toEqual(9);
diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js
index a4d68feffdc4..7a179c760af2 100644
--- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js
+++ b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js
@@ -16,32 +16,31 @@ import {
unit,
} from '../../../../style/variables';
import { tint } from 'polished';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-function getIconColor(type) {
+function getIconColor(type, theme) {
switch (type) {
case 'field':
- return theme.euiColorVis7;
+ return theme.eui.euiColorVis7;
case 'value':
- return theme.euiColorVis0;
+ return theme.eui.euiColorVis0;
case 'operator':
- return theme.euiColorVis1;
+ return theme.eui.euiColorVis1;
case 'conjunction':
- return theme.euiColorVis3;
+ return theme.eui.euiColorVis3;
case 'recentSearch':
- return theme.euiColorMediumShade;
+ return theme.eui.euiColorMediumShade;
}
}
const Description = styled.div`
- color: ${theme.euiColorDarkShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkShade};
p {
display: inline;
span {
font-family: ${fontFamilyCode};
- color: ${theme.euiColorFullShade};
+ color: ${({ theme }) => theme.eui.euiColorFullShade};
padding: 0 ${px(units.quarter)};
display: inline-block;
}
@@ -53,25 +52,25 @@ const ListItem = styled.li`
height: ${px(units.double)};
align-items: center;
display: flex;
- background: ${(props) =>
- props.selected ? theme.euiColorLightestShade : 'initial'};
+ background: ${({ selected, theme }) =>
+ selected ? theme.eui.euiColorLightestShade : 'initial'};
cursor: pointer;
border-radius: ${px(units.quarter)};
${Description} {
p span {
- background: ${(props) =>
- props.selected
- ? theme.euiColorEmptyShade
- : theme.euiColorLightestShade};
+ background: ${({ selected, theme }) =>
+ selected
+ ? theme.eui.euiColorEmptyShade
+ : theme.eui.euiColorLightestShade};
}
}
`;
const Icon = styled.div`
flex: 0 0 ${px(units.double)};
- background: ${(props) => tint(0.1, getIconColor(props.type))};
- color: ${(props) => getIconColor(props.type)};
+ background: ${({ type, theme }) => tint(0.1, getIconColor(type, theme))};
+ color: ${({ type, theme }) => getIconColor(type, theme)};
width: 100%;
height: 100%;
text-align: center;
@@ -80,7 +79,7 @@ const Icon = styled.div`
const TextValue = styled.div`
flex: 0 0 ${px(unit * 16)};
- color: ${theme.euiColorDarkestShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkestShade};
padding: 0 ${px(units.half)};
`;
diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js
index fbc7cbcb7853..5e1434e792e1 100644
--- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js
+++ b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js
@@ -11,16 +11,15 @@ import { isEmpty } from 'lodash';
import Suggestion from './Suggestion';
import { units, px, unit } from '../../../../style/variables';
import { tint } from 'polished';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
const List = styled.ul`
width: 100%;
- border: 1px solid ${theme.euiColorLightShade};
+ border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
border-radius: ${px(units.quarter)};
box-shadow: 0px ${px(units.quarter)} ${px(units.double)}
- ${tint(0.1, theme.euiColorFullShade)};
+ ${({ theme }) => tint(0.1, theme.eui.euiColorFullShade)};
position: absolute;
- background: #fff;
+ background: ${({ theme }) => theme.eui.euiColorEmptyShade};
z-index: 10;
left: 0;
max-height: ${px(unit * 20)};
diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
index 45b32a82c2f8..46fd5d925699 100644
--- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
@@ -18,7 +18,6 @@ import {
EuiFlexGroup,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import styled from 'styled-components';
import { FilterBadgeList } from './FilterBadgeList';
import { unit, px } from '../../../../style/variables';
@@ -39,9 +38,9 @@ const SelectContainer = styled.div`
`;
const Counter = styled.div`
- border-radius: ${theme.euiBorderRadius};
- background: ${theme.euiColorLightShade};
- padding: 0 ${theme.paddingSizes.xs};
+ border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
+ background: ${({ theme }) => theme.eui.euiColorLightShade};
+ padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs};
`;
const ApplyButton = styled(EuiButton)`
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx
index 7ffcce0c29ee..4d6305dd7534 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx
@@ -5,8 +5,9 @@
*/
import React from 'react';
-import { mount, shallow } from 'enzyme';
+import { shallow } from 'enzyme';
import { CauseStacktrace } from './CauseStacktrace';
+import { mountWithTheme } from '../../../utils/testHelpers';
describe('CauseStacktrace', () => {
describe('render', () => {
@@ -15,7 +16,7 @@ describe('CauseStacktrace', () => {
const props = { id: 'testId', message: 'testMessage' };
expect(
- mount( ).find('CausedBy')
+ mountWithTheme( ).find('CausedBy')
).toHaveLength(1);
});
});
@@ -28,7 +29,7 @@ describe('CauseStacktrace', () => {
};
expect(
- mount( )
+ mountWithTheme( )
.find('EuiTitle span')
.text()
).toEqual('…');
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx
index 9b80ab648eca..c70467c6d031 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import styled from 'styled-components';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { EuiAccordion, EuiTitle } from '@elastic/eui';
import { px, unit } from '../../../style/variables';
@@ -15,18 +14,18 @@ import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackfram
// @ts-ignore Styled Components has trouble inferring the types of the default props here.
const Accordion = styled(EuiAccordion)`
- border-top: ${theme.euiBorderThin};
+ border-top: ${({ theme }) => theme.eui.euiBorderThin};
`;
const CausedByContainer = styled('h5')`
- padding: ${theme.spacerSizes.s} 0;
+ padding: ${({ theme }) => theme.eui.spacerSizes.s} 0;
`;
const CausedByHeading = styled('span')`
- color: ${theme.textColors.subdued};
+ color: ${({ theme }) => theme.eui.textColors.subdued};
display: block;
- font-size: ${theme.euiFontSizeXS};
- font-weight: ${theme.euiFontWeightBold};
+ font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
+ font-weight: ${({ theme }) => theme.eui.euiFontWeightBold};
text-transform: uppercase;
`;
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx
index 6ce2c6b7fc8f..edada6012d2e 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { size } from 'lodash';
import { tint } from 'polished';
import React from 'react';
@@ -41,7 +40,7 @@ const LineHighlight = styled.div<{ lineNumber: number }>`
height: ${px(units.eighth * 9)};
top: ${(props) => px(props.lineNumber * LINE_HEIGHT)};
pointer-events: none;
- background-color: ${tint(0.1, theme.euiColorWarning)};
+ background-color: ${({ theme }) => tint(0.1, theme.eui.euiColorWarning)};
`;
const LineNumberContainer = styled.div<{ isLibraryFrame: boolean }>`
@@ -49,10 +48,10 @@ const LineNumberContainer = styled.div<{ isLibraryFrame: boolean }>`
top: 0;
left: 0;
border-radius: ${borderRadius};
- background: ${(props) =>
- props.isLibraryFrame
- ? theme.euiColorEmptyShade
- : theme.euiColorLightestShade};
+ background: ${({ isLibraryFrame, theme }) =>
+ isLibraryFrame
+ ? theme.eui.euiColorEmptyShade
+ : theme.eui.euiColorLightestShade};
`;
const LineNumber = styled.div<{ highlight: boolean }>`
@@ -60,12 +59,12 @@ const LineNumber = styled.div<{ highlight: boolean }>`
min-width: ${px(units.eighth * 21)};
padding-left: ${px(units.half)};
padding-right: ${px(units.quarter)};
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
line-height: ${px(unit + units.eighth)};
text-align: right;
- border-right: 1px solid ${theme.euiColorLightShade};
- background-color: ${(props) =>
- props.highlight ? tint(0.1, theme.euiColorWarning) : null};
+ border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
+ background-color: ${({ highlight, theme }) =>
+ highlight ? tint(0.1, theme.eui.euiColorWarning) : null};
&:last-of-type {
border-radius: 0 0 0 ${borderRadius};
@@ -76,7 +75,7 @@ const LineContainer = styled.div`
overflow: auto;
margin: 0 0 0 ${px(units.eighth * 21)};
padding: 0;
- background-color: ${theme.euiColorEmptyShade};
+ background-color: ${({ theme }) => theme.eui.euiColorEmptyShade};
&:last-of-type {
border-radius: 0 0 ${borderRadius} 0;
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx
index 4467fe7ad615..48580146c6fe 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx
@@ -4,25 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { fontFamilyCode, fontSize, px, units } from '../../../style/variables';
const FileDetails = styled.div`
- color: ${theme.euiColorDarkShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkShade};
padding: ${px(units.half)} 0;
font-family: ${fontFamilyCode};
font-size: ${fontSize};
`;
const LibraryFrameFileDetail = styled.span`
- color: ${theme.euiColorDarkShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkShade};
`;
const AppFrameFileDetail = styled.span`
- color: ${theme.euiColorFullShade};
+ color: ${({ theme }) => theme.eui.euiColorFullShade};
`;
interface Props {
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx
index e62a4ecf3993..0c60a9aaace0 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
import styled from 'styled-components';
import { EuiAccordion } from '@elastic/eui';
@@ -25,12 +24,12 @@ const ContextContainer = styled.div<{ isLibraryFrame: boolean }>`
position: relative;
font-family: ${fontFamilyCode};
font-size: ${fontSize};
- border: 1px solid ${theme.euiColorLightShade};
+ border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
border-radius: ${borderRadius};
- background: ${(props) =>
- props.isLibraryFrame
- ? theme.euiColorEmptyShade
- : theme.euiColorLightestShade};
+ background: ${({ isLibraryFrame, theme }) =>
+ isLibraryFrame
+ ? theme.eui.euiColorEmptyShade
+ : theme.eui.euiColorLightestShade};
`;
interface Props {
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
index ec5fb39f83f8..4bd6d361d671 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import styled from 'styled-components';
import { EuiAccordion } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -15,7 +14,7 @@ import { KeyValueTable } from '../KeyValueTable';
import { flattenObject } from '../../../utils/flattenObject';
const VariablesContainer = styled.div`
- background: ${theme.euiColorEmptyShade};
+ background: ${({ theme }) => theme.eui.euiColorEmptyShade};
border-radius: 0 0 ${borderRadius} ${borderRadius};
padding: ${px(units.half)} ${px(unit)};
`;
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx
index 478f9cfe921d..1c5db4e82f06 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { mount, ReactWrapper, shallow } from 'enzyme';
import React from 'react';
+import { ReactWrapper, shallow } from 'enzyme';
import { IStackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe';
+import { mountWithTheme } from '../../../../utils/testHelpers';
import { Stackframe } from '../Stackframe';
import stacktracesMock from './stacktraces.json';
@@ -15,7 +16,9 @@ describe('Stackframe', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const stackframe = stacktracesMock[0];
- wrapper = mount( );
+ wrapper = mountWithTheme(
+
+ );
});
it('should render correctly', () => {
@@ -37,7 +40,9 @@ describe('Stackframe', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const stackframe = { line: {} } as IStackframe;
- wrapper = mount( );
+ wrapper = mountWithTheme(
+
+ );
});
it('should render only FrameHeading', () => {
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap
index 0656134e2f1f..86e6b188ef2a 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap
@@ -383,8 +383,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": 1280172402,
- "componentId": "sc-fzozJi",
+ "baseHash": -2021127760,
+ "componentId": "sc-fzoLsD",
"isStatic": false,
"rules": Array [
"
@@ -401,7 +401,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-fzozJi",
+ "styledComponentId": "sc-fzoLsD",
"target": "code",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -413,8 +413,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": -1923298833,
- "componentId": "sc-AxmLO",
+ "baseHash": 1280172402,
+ "componentId": "sc-fzozJi",
"isStatic": false,
"rules": Array [
"
@@ -439,7 +439,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-AxmLO",
+ "styledComponentId": "sc-fzozJi",
"target": "pre",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -608,8 +608,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": 1280172402,
- "componentId": "sc-fzozJi",
+ "baseHash": -2021127760,
+ "componentId": "sc-fzoLsD",
"isStatic": false,
"rules": Array [
"
@@ -626,7 +626,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-fzozJi",
+ "styledComponentId": "sc-fzoLsD",
"target": "code",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -638,8 +638,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": -1923298833,
- "componentId": "sc-AxmLO",
+ "baseHash": 1280172402,
+ "componentId": "sc-fzozJi",
"isStatic": false,
"rules": Array [
"
@@ -664,7 +664,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-AxmLO",
+ "styledComponentId": "sc-fzozJi",
"target": "pre",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -834,8 +834,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": 1280172402,
- "componentId": "sc-fzozJi",
+ "baseHash": -2021127760,
+ "componentId": "sc-fzoLsD",
"isStatic": false,
"rules": Array [
"
@@ -852,7 +852,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-fzozJi",
+ "styledComponentId": "sc-fzoLsD",
"target": "code",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -864,8 +864,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": -1923298833,
- "componentId": "sc-AxmLO",
+ "baseHash": 1280172402,
+ "componentId": "sc-fzozJi",
"isStatic": false,
"rules": Array [
"
@@ -890,7 +890,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-AxmLO",
+ "styledComponentId": "sc-fzozJi",
"target": "pre",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -1070,8 +1070,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": 1280172402,
- "componentId": "sc-fzozJi",
+ "baseHash": -2021127760,
+ "componentId": "sc-fzoLsD",
"isStatic": false,
"rules": Array [
"
@@ -1088,7 +1088,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-fzozJi",
+ "styledComponentId": "sc-fzoLsD",
"target": "code",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -1100,8 +1100,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": -1923298833,
- "componentId": "sc-AxmLO",
+ "baseHash": 1280172402,
+ "componentId": "sc-fzozJi",
"isStatic": false,
"rules": Array [
"
@@ -1126,7 +1126,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-AxmLO",
+ "styledComponentId": "sc-fzozJi",
"target": "pre",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -1323,8 +1323,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": 1280172402,
- "componentId": "sc-fzozJi",
+ "baseHash": -2021127760,
+ "componentId": "sc-fzoLsD",
"isStatic": false,
"rules": Array [
"
@@ -1341,7 +1341,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-fzozJi",
+ "styledComponentId": "sc-fzoLsD",
"target": "code",
"toString": [Function],
"warnTooManyClasses": [Function],
@@ -1353,8 +1353,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
- "baseHash": -1923298833,
- "componentId": "sc-AxmLO",
+ "baseHash": 1280172402,
+ "componentId": "sc-fzozJi",
"isStatic": false,
"rules": Array [
"
@@ -1379,7 +1379,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`]
"foldedComponentIds": Array [],
"render": [Function],
"shouldForwardProp": undefined,
- "styledComponentId": "sc-AxmLO",
+ "styledComponentId": "sc-fzozJi",
"target": "pre",
"toString": [Function],
"warnTooManyClasses": [Function],
diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx
index 8bfea721c854..b9ed6d424d66 100644
--- a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx
@@ -6,7 +6,6 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiToolTip } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
import styled from 'styled-components';
import {
@@ -32,7 +31,7 @@ const TooltipFieldName = styled.span`
const PropertyLabel = styled.div`
margin-bottom: ${px(units.half)};
font-size: ${fontSizes.small};
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
span {
cursor: help;
diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx
index 436e0132148a..b6ea6a714017 100644
--- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx
@@ -7,7 +7,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { EuiBadge } from '@elastic/eui';
-import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json';
+import { useTheme } from '../../../hooks/useTheme';
import { px } from '../../../../public/style/variables';
import { units } from '../../../style/variables';
@@ -19,12 +19,16 @@ const Badge = (styled(EuiBadge)`
margin-top: ${px(units.eighth)};
` as unknown) as typeof EuiBadge;
-export const ErrorCountSummaryItemBadge = ({ count }: Props) => (
-
- {i18n.translate('xpack.apm.transactionDetails.errorCount', {
- defaultMessage:
- '{errorCount, number} {errorCount, plural, one {Error} other {Errors}}',
- values: { errorCount: count },
- })}
-
-);
+export const ErrorCountSummaryItemBadge = ({ count }: Props) => {
+ const theme = useTheme();
+
+ return (
+
+ {i18n.translate('xpack.apm.transactionDetails.errorCount', {
+ defaultMessage:
+ '{errorCount, number} {errorCount, plural, one {Error} other {Errors}}',
+ values: { errorCount: count },
+ })}
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.test.tsx
index 09bf90f6d4b1..e6a4f2f70708 100644
--- a/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.test.tsx
@@ -5,8 +5,8 @@
*/
import React from 'react';
-import { shallow, render } from 'enzyme';
import { UserAgentSummaryItem } from './UserAgentSummaryItem';
+import { mountWithTheme } from '../../../utils/testHelpers';
describe('UserAgentSummaryItem', () => {
describe('render', () => {
@@ -14,14 +14,14 @@ describe('UserAgentSummaryItem', () => {
it('renders', () => {
expect(() =>
- shallow( )
+ mountWithTheme( )
).not.toThrowError();
});
describe('with a version', () => {
it('shows the version', () => {
const p = { ...props, version: '1.0' };
- const wrapper = render( );
+ const wrapper = mountWithTheme( );
expect(wrapper.text()).toContain('(1.0)');
});
diff --git a/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.tsx b/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.tsx
index 1b565a60697e..753d605e7b2c 100644
--- a/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Summary/UserAgentSummaryItem.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import styled from 'styled-components';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { UserAgent } from '../../../../typings/es_schemas/raw/fields/user_agent';
@@ -14,7 +13,7 @@ import { UserAgent } from '../../../../typings/es_schemas/raw/fields/user_agent'
type UserAgentSummaryItemProps = UserAgent;
const Version = styled('span')`
- font-size: ${theme.euiFontSizeS};
+ font-size: ${({ theme }) => theme.eui.euiFontSizeS};
`;
export function UserAgentSummaryItem({
diff --git a/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx
index 33f5752b6389..26087e1fd85c 100644
--- a/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx
@@ -6,16 +6,19 @@
import React from 'react';
import { ErrorCountSummaryItemBadge } from '../ErrorCountSummaryItemBadge';
-import { render } from '@testing-library/react';
-import { expectTextsInDocument } from '../../../../utils/testHelpers';
+import {
+ expectTextsInDocument,
+ renderWithTheme,
+} from '../../../../utils/testHelpers';
describe('ErrorCountSummaryItemBadge', () => {
it('shows singular error message', () => {
- const component = render( );
+ const component = renderWithTheme( );
expectTextsInDocument(component, ['1 Error']);
});
+
it('shows plural error message', () => {
- const component = render( );
+ const component = renderWithTheme( );
expectTextsInDocument(component, ['2 Errors']);
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/Summary/index.tsx b/x-pack/plugins/apm/public/components/shared/Summary/index.tsx
index ce6935d1858a..55ac525d7119 100644
--- a/x-pack/plugins/apm/public/components/shared/Summary/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Summary/index.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
-import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { px, units } from '../../../../public/style/variables';
import { Maybe } from '../../../../typings/common';
@@ -14,12 +13,9 @@ interface Props {
items: Array>;
}
-// TODO: Light/Dark theme (@see https://github.com/elastic/kibana/issues/44840)
-const theme = euiLightVars;
-
const Item = styled(EuiFlexItem)`
flex-wrap: nowrap;
- border-right: 1px solid ${theme.euiColorLightShade};
+ border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
padding-right: ${px(units.half)};
flex-flow: row nowrap;
line-height: 1.5;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx
index d95a4e418628..ed57692d70a6 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx
@@ -5,7 +5,6 @@
*/
import React from 'react';
import { VerticalGridLines } from 'react-vis';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import {
EuiIcon,
EuiToolTip,
@@ -14,6 +13,7 @@ import {
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { useTheme } from '../../../../hooks/useTheme';
import { Maybe } from '../../../../../typings/common';
import { Annotation } from '../../../../../common/annotations';
import { PlotValues, SharedPlot } from './plotUtils';
@@ -26,16 +26,15 @@ interface Props {
overlay: Maybe;
}
-const style = {
- stroke: theme.euiColorSecondary,
- strokeDasharray: 'none',
-};
-
-export function AnnotationsPlot(props: Props) {
- const { plotValues, annotations } = props;
-
+export const AnnotationsPlot = ({ plotValues, annotations }: Props) => {
+ const theme = useTheme();
const tickValues = annotations.map((annotation) => annotation['@timestamp']);
+ const style = {
+ stroke: theme.eui.euiColorSecondary,
+ strokeDasharray: 'none',
+ };
+
return (
<>
@@ -65,10 +64,10 @@ export function AnnotationsPlot(props: Props) {
}
>
-
+
))}
>
);
-}
+};
diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
index bf6cf083e00e..7d37d2d67269 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
@@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import PropTypes from 'prop-types';
import React from 'react';
+import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Legend } from '../Legend';
+import { useTheme } from '../../../../hooks/useTheme';
import {
unit,
units,
@@ -15,7 +16,6 @@ import {
px,
truncate,
} from '../../../../style/variables';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { EuiIcon } from '@elastic/eui';
@@ -36,7 +36,7 @@ const Container = styled.div`
const LegendContent = styled.span`
white-space: nowrap;
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
display: flex;
`;
@@ -47,13 +47,13 @@ const TruncatedLabel = styled.span`
const SeriesValue = styled.span`
margin-left: ${px(units.quarter)};
- color: ${theme.euiColorFullShade};
+ color: ${({ theme }) => theme.eui.euiColorFullShade};
display: inline-block;
`;
const MoreSeriesContainer = styled.div`
font-size: ${fontSizes.small};
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
`;
function MoreSeries({ hiddenSeriesCount }) {
@@ -80,6 +80,8 @@ export default function Legends({
showAnnotations,
onAnnotationsToggle,
}) {
+ const theme = useTheme();
+
if (noHits && !hasAnnotations) {
return null;
}
@@ -90,6 +92,7 @@ export default function Legends({
if (serie.hideLegend) {
return null;
}
+
const text = (
{truncateLegends ? (
@@ -129,11 +132,11 @@ export default function Legends({
}
indicator={() => (
-
+
)}
disabled={!showAnnotations}
- color={theme.euiColorSecondary}
+ color={theme.eui.euiColorSecondary}
/>
)}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/CustomPlot.test.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/CustomPlot.test.js
index ad1d73f2b766..d2e237b086bb 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/CustomPlot.test.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/CustomPlot.test.js
@@ -4,10 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { mount } from 'enzyme';
import moment from 'moment';
import React from 'react';
-import { toJson } from '../../../../../utils/testHelpers';
+import { toJson, mountWithTheme } from '../../../../../utils/testHelpers';
import { InnerCustomPlot } from '../index';
import responseWithData from './responseWithData.json';
import VoronoiPlot from '../VoronoiPlot';
@@ -30,7 +29,7 @@ describe('when response has data', () => {
onHover = jest.fn();
onMouseLeave = jest.fn();
onSelectionEnd = jest.fn();
- wrapper = mount(
+ wrapper = mountWithTheme(
{
beforeEach(() => {
const series = getEmptySeries(1451606400000, 1451610000000);
- wrapper = mount(
+ wrapper = mountWithTheme(
{
@@ -26,7 +25,7 @@ describe('Histogram', () => {
const xMax = d3.max(buckets, (d) => d.x);
const timeFormatter = getDurationFormatter(xMax);
- wrapper = mount(
+ wrapper = mountWithTheme(
{
});
describe('Initially', () => {
- it('should have default state', () => {
- expect(wrapper.state()).toEqual({ hoveredBucket: {} });
- });
-
it('should have default markup', () => {
expect(toJson(wrapper)).toMatchSnapshot();
});
@@ -86,23 +81,6 @@ describe('Histogram', () => {
expect(tooltips.prop('y')).toEqual(27.5);
});
- it('should update state with "hoveredBucket"', () => {
- expect(wrapper.state()).toEqual({
- hoveredBucket: {
- samples: [
- {
- transactionId: '99c50a5b-44b4-4289-a3d1-a2815d128192',
- },
- ],
- style: { cursor: 'pointer' },
- xCenter: 869010,
- x0: 811076,
- x: 926944,
- y: 49,
- },
- });
- });
-
it('should have correct markup for tooltip', () => {
const tooltips = wrapper.find('Tooltip');
expect(toJson(tooltips)).toMatchSnapshot();
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx
index 1626c62bfdf4..a00c46bcf324 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
import styled from 'styled-components';
+import { useTheme } from '../../../../hooks/useTheme';
import { fontSizes, px, units } from '../../../../style/variables';
export enum Shape {
@@ -20,11 +20,12 @@ interface ContainerProps {
clickable: boolean;
disabled: boolean;
}
+
const Container = styled.div`
display: flex;
align-items: center;
font-size: ${(props) => props.fontSize};
- color: ${theme.euiColorDarkShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkShade};
cursor: ${(props) => (props.clickable ? 'pointer' : 'initial')};
opacity: ${(props) => (props.disabled ? 0.4 : 1)};
user-select: none;
@@ -36,6 +37,7 @@ interface IndicatorProps {
shape: Shape;
withMargin: boolean;
}
+
export const Indicator = styled.span`
width: ${(props) => px(props.radius)};
height: ${(props) => px(props.radius)};
@@ -61,7 +63,7 @@ interface Props {
export const Legend: React.FC = ({
onClick,
text,
- color = theme.euiColorVis1,
+ color,
fontSize = fontSizes.small,
radius = units.minus - 1,
disabled = false,
@@ -70,6 +72,9 @@ export const Legend: React.FC = ({
indicator,
...rest
}) => {
+ const theme = useTheme();
+ const indicatorColor = color || theme.eui.euiColorVis1;
+
return (
= ({
indicator()
) : (
{
const mark = {
@@ -16,8 +17,14 @@ describe('AgentMarker', () => {
type: 'agentMark',
verticalLine: true,
} as AgentMark;
+
it('renders', () => {
- const component = shallow( );
+ const component = shallow(
+
+
+
+ );
+
expect(component).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx
index ffdbfe6cce7e..d2dea39b83d8 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx
@@ -4,22 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiToolTip } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
+import { EuiToolTip } from '@elastic/eui';
import styled from 'styled-components';
+import { useTheme } from '../../../../../hooks/useTheme';
import { px, units } from '../../../../../style/variables';
import { asDuration } from '../../../../../utils/formatters';
import { Legend } from '../../Legend';
import { AgentMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks';
const NameContainer = styled.div`
- border-bottom: 1px solid ${theme.euiColorMediumShade};
+ border-bottom: 1px solid ${({ theme }) => theme.eui.euiColorMediumShade};
padding-bottom: ${px(units.half)};
`;
const TimeContainer = styled.div`
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
padding-top: ${px(units.half)};
`;
@@ -28,6 +28,8 @@ interface Props {
}
export const AgentMarker: React.FC = ({ mark }) => {
+ const theme = useTheme();
+
return (
<>
= ({ mark }) => {
}
>
-
+
>
);
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx
index 7dc68910c6be..922796afd39b 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx
@@ -7,9 +7,12 @@
import React from 'react';
import { ErrorMarker } from './ErrorMarker';
import { ErrorMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks';
-import { render, fireEvent } from '@testing-library/react';
+import { fireEvent } from '@testing-library/react';
import { act } from '@testing-library/react-hooks';
-import { expectTextsInDocument } from '../../../../../utils/testHelpers';
+import {
+ expectTextsInDocument,
+ renderWithTheme,
+} from '../../../../../utils/testHelpers';
describe('ErrorMarker', () => {
const mark = ({
@@ -33,7 +36,7 @@ describe('ErrorMarker', () => {
} as unknown) as ErrorMark;
function openPopover(errorMark: ErrorMark) {
- const component = render( );
+ const component = renderWithTheme( );
act(() => {
fireEvent.click(component.getByTestId('popover'));
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx
index e3310c273a55..d8e056deb769 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiPopover, EuiText } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import React, { useState } from 'react';
+import { EuiPopover, EuiText } from '@elastic/eui';
import styled from 'styled-components';
+import { useTheme } from '../../../../../hooks/useTheme';
import {
TRACE_ID,
TRANSACTION_ID,
@@ -54,6 +54,7 @@ function truncateMessage(errorMessage?: string) {
}
export const ErrorMarker: React.FC = ({ mark }) => {
+ const theme = useTheme();
const { urlParams } = useUrlParams();
const [isPopoverOpen, showPopover] = useState(false);
@@ -63,7 +64,7 @@ export const ErrorMarker: React.FC = ({ mark }) => {
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap
index 0d370d661414..005510a6dafc 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap
@@ -1,26 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AgentMarker renders 1`] = `
-
-
-
- agent
-
-
- 1,000 μs
-
-
+
+
-
-
-
+ />
+
`;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx
index a0dffa571003..03ddc3f65682 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx
@@ -4,10 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { mount } from 'enzyme';
import React from 'react';
import { StickyContainer } from 'react-sticky';
-import { mockMoment, toJson } from '../../../../utils/testHelpers';
+import {
+ mountWithTheme,
+ mockMoment,
+ toJson,
+} from '../../../../utils/testHelpers';
import { Timeline } from '.';
describe('Timeline', () => {
@@ -50,7 +53,7 @@ describe('Timeline', () => {
],
};
- const wrapper = mount(
+ const wrapper = mountWithTheme(
@@ -74,7 +77,7 @@ describe('Timeline', () => {
};
const mountTimeline = () =>
- mount(
+ mountWithTheme(
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx
index b6d2d8218c24..a9c36634381d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx
@@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import { inRange } from 'lodash';
import React, { ReactNode } from 'react';
+import { inRange } from 'lodash';
import { Sticky } from 'react-sticky';
import { XAxis, XYPlot } from 'react-vis';
+import { useTheme } from '../../../../hooks/useTheme';
import { px } from '../../../../style/variables';
import { getDurationFormatter } from '../../../../utils/formatters';
import { Mark } from './';
@@ -42,11 +42,12 @@ interface TimelineAxisProps {
topTraceDuration: number;
}
-export function TimelineAxis({
+export const TimelineAxis = ({
plotValues,
marks = [],
topTraceDuration,
-}: TimelineAxisProps) {
+}: TimelineAxisProps) => {
+ const theme = useTheme();
const { margins, tickValues, width, xDomain, xMax, xScale } = plotValues;
const tickFormatter = getDurationFormatter(xMax);
const xAxisTickValues = getXAxisTickValues(tickValues, topTraceDuration);
@@ -59,7 +60,7 @@ export function TimelineAxis({
tickFormatter(time).formatted}
tickPadding={20}
style={{
- text: { fill: theme.euiColorDarkShade },
+ text: { fill: theme.eui.euiColorDarkShade },
}}
/>
@@ -106,4 +107,4 @@ export function TimelineAxis({
}}
);
-}
+};
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx
index 19d9eb48e0b3..0753cb318d3a 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import React, { PureComponent } from 'react';
+import React from 'react';
import { VerticalGridLines, XYPlot } from 'react-vis';
+import { useTheme } from '../../../../hooks/useTheme';
import { Mark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks';
import { PlotValues } from './plotUtils';
@@ -16,54 +16,51 @@ interface VerticalLinesProps {
topTraceDuration: number;
}
-export class VerticalLines extends PureComponent
{
- render() {
- const { topTraceDuration, marks = [] } = this.props;
- const {
- width,
- height,
- margins,
- xDomain,
- tickValues,
- } = this.props.plotValues;
+export const VerticalLines = ({
+ topTraceDuration,
+ plotValues,
+ marks = [],
+}: VerticalLinesProps) => {
+ const { width, height, margins, xDomain, tickValues } = plotValues;
- const markTimes = marks
- .filter((mark) => mark.verticalLine)
- .map(({ offset }) => offset);
+ const markTimes = marks
+ .filter((mark) => mark.verticalLine)
+ .map(({ offset }) => offset);
- return (
-
+
-
-
+
+
+
+ {topTraceDuration > 0 && (
-
- {topTraceDuration > 0 && (
-
- )}
-
-
- );
- }
-}
+ )}
+
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Tooltip/index.js b/x-pack/plugins/apm/public/components/shared/charts/Tooltip/index.js
index 6af7a7691974..7ee0c6280526 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Tooltip/index.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/Tooltip/index.js
@@ -18,25 +18,24 @@ import {
fontSizes,
} from '../../../../style/variables';
import { Legend } from '../Legend';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
import { asAbsoluteDateTime } from '../../../../utils/formatters';
const TooltipElm = styled.div`
margin: 0 ${px(unit)};
transform: translateY(-50%);
- border: 1px solid ${theme.euiColorLightShade};
- background: ${theme.euiColorEmptyShade};
+ border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
+ background: ${({ theme }) => theme.eui.euiColorEmptyShade};
border-radius: ${borderRadius};
font-size: ${fontSize};
- color: ${theme.euiColorFullShade};
+ color: ${({ theme }) => theme.eui.euiColorFullShade};
`;
const Header = styled.div`
- background: ${theme.euiColorLightestShade};
- border-bottom: 1px solid ${theme.euiColorLightShade};
+ background: ${({ theme }) => theme.eui.euiColorLightestShade};
+ border-bottom: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
border-radius: ${borderRadius} ${borderRadius} 0 0;
padding: ${px(units.half)};
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
`;
const Content = styled.div`
@@ -46,7 +45,7 @@ const Content = styled.div`
`;
const Footer = styled.div`
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
margin: ${px(units.half)};
font-size: ${fontSizes.small};
`;
@@ -59,13 +58,13 @@ const LegendContainer = styled.div`
`;
const LegendGray = styled(Legend)`
- color: ${theme.euiColorMediumShade};
+ color: ${({ theme }) => theme.eui.euiColorMediumShade};
padding-bottom: 0;
padding-right: ${px(units.half)};
`;
const Value = styled.div`
- color: ${theme.euiColorDarkShade};
+ color: ${({ theme }) => theme.eui.euiColorDarkShade};
font-size: ${fontSize};
`;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx
index ebd6061831f4..a9a9343dde6b 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx
@@ -13,8 +13,9 @@ import React, {
} from 'react';
import { Map, NavigationControl, Popup } from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
-import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { shade, tint } from 'polished';
+import { EuiTheme } from '../../../../../../../observability/public';
+import { useTheme } from '../../../../../hooks/useTheme';
import { ChoroplethToolTip } from './ChoroplethToolTip';
interface ChoroplethItem {
@@ -47,8 +48,8 @@ const MAPBOX_STYLE =
const GEOJSON_SOURCE =
'https://vector.maps.elastic.co/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree&my_app_name=ems-landing&my_app_version=7.2.0';
-export function getProgressionColor(scale: number) {
- const baseColor = euiLightVars.euiColorPrimary;
+export function getProgressionColor(scale: number, theme: EuiTheme) {
+ const baseColor = theme.eui.euiColorPrimary;
const adjustedScale = 0.75 * scale + 0.05; // prevents pure black & white as min/max colors.
if (adjustedScale < 0.5) {
return tint(adjustedScale * 2, baseColor);
@@ -66,8 +67,8 @@ const getMax = (items: ChoroplethItem[]) =>
Math.max(...items.map((item) => item.value));
export const ChoroplethMap: React.FC = (props) => {
+ const theme = useTheme();
const { items } = props;
-
const containerRef = useRef(null);
const [map, setMap] = useState(null);
const popupRef = useRef(null);
@@ -215,7 +216,7 @@ export const ChoroplethMap: React.FC = (props) => {
const stops = items.map(({ key, value }) => [
key,
- getProgressionColor(getValueScale(value)),
+ getProgressionColor(getValueScale(value), theme),
]);
const fillColor: mapboxgl.FillPaint['fill-color'] = {
@@ -238,7 +239,7 @@ export const ChoroplethMap: React.FC = (props) => {
},
symbolLayer ? symbolLayer.id : undefined
);
- }, [map, items, getValueScale]);
+ }, [map, items, theme, getValueScale]);
// side effect to only render the Popup when hovering a region with a matching item
useEffect(() => {
diff --git a/x-pack/plugins/apm/public/hooks/useTheme.tsx b/x-pack/plugins/apm/public/hooks/useTheme.tsx
new file mode 100644
index 000000000000..e372a764a950
--- /dev/null
+++ b/x-pack/plugins/apm/public/hooks/useTheme.tsx
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useContext } from 'react';
+import { ThemeContext } from 'styled-components';
+import { EuiTheme } from '../../../observability/public';
+
+export function useTheme(): EuiTheme {
+ const theme = useContext(ThemeContext);
+ return theme;
+}
diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx
index 4a9ded08f7cc..8e7f98796678 100644
--- a/x-pack/plugins/apm/public/utils/testHelpers.tsx
+++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx
@@ -6,18 +6,19 @@
/* global jest */
-import { ReactWrapper } from 'enzyme';
+import React from 'react';
+import { ReactWrapper, mount, MountRendererProps } from 'enzyme';
import enzymeToJson from 'enzyme-to-json';
import { Location } from 'history';
import moment from 'moment';
import { Moment } from 'moment-timezone';
-import React from 'react';
import { render, waitForElement } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { APMConfig } from '../../server';
import { LocationProvider } from '../context/LocationContext';
import { PromiseReturnType } from '../../typings/common';
+import { EuiThemeProvider } from '../../../observability/public';
import {
ESFilter,
ESSearchResponse,
@@ -180,3 +181,27 @@ export async function inspectSearchParams(
}
export type SearchParamsMock = PromiseReturnType;
+
+export function renderWithTheme(
+ component: React.ReactNode,
+ params?: any,
+ { darkMode = false } = {}
+) {
+ return render(
+ {component} ,
+ params
+ );
+}
+
+export function mountWithTheme(
+ tree: React.ReactElement,
+ { darkMode = false } = {}
+) {
+ const WrappingThemeProvider = (props: any) => (
+ {props.children}
+ );
+
+ return mount(tree, {
+ wrappingComponent: WrappingThemeProvider,
+ } as MountRendererProps);
+}
From e65163b630f7186777dc7f2955bd95dbe8c3b14c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Tue, 23 Jun 2020 08:12:19 +0100
Subject: [PATCH 29/33] [APM] Add error rate chart to Errors overview and
detail views (#67327) (#69490)
* creating error rate chart
* adding error line chart
* creating error rate chart
* using date_histogram
* reapplying prettier style
* changing to theme color
* dont sync tooltips
* adding avg on error charts
* addressing pr comments
* adding possibility to disable legend toggle
* removing x-axis ticks from histogram
* return no percent when transaction count doesn return hits
* addressing PR comments
* addressing PR comments
* returning null when there is no transaction count
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../ErrorGroupDetails/Distribution/index.tsx | 24 +-
.../app/ErrorGroupDetails/index.tsx | 107 ++++----
.../app/ErrorGroupOverview/index.tsx | 122 ++++-----
.../shared/charts/CustomPlot/Legends.js | 4 +-
.../shared/charts/CustomPlot/index.js | 5 +-
.../shared/charts/ErrorRateChart/index.tsx | 100 ++++++++
.../__snapshots__/Histogram.test.js.snap | 28 +--
.../shared/charts/Histogram/index.js | 232 +++++++++++-------
.../apm/server/lib/errors/get_error_rate.ts | 109 ++++++++
.../apm/server/routes/create_apm_api.ts | 2 +
x-pack/plugins/apm/server/routes/errors.ts | 24 ++
11 files changed, 534 insertions(+), 223 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
create mode 100644 x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
index 796f2992236f..d71d5f2cb480 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
@@ -5,9 +5,12 @@
*/
import { EuiTitle } from '@elastic/eui';
+import theme from '@elastic/eui/dist/eui_theme_light.json';
+import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
-import { scaleUtc } from 'd3-scale';
import d3 from 'd3';
+import { scaleUtc } from 'd3-scale';
+import mean from 'lodash.mean';
import React from 'react';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';
import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
@@ -17,7 +20,7 @@ import { EmptyMessage } from '../../../shared/EmptyMessage';
interface IBucket {
key: number;
- count: number;
+ count: number | undefined;
}
// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse)
@@ -30,7 +33,7 @@ interface IDistribution {
interface FormattedBucket {
x0: number;
x: number;
- y: number;
+ y: number | undefined;
}
export function getFormattedBuckets(
@@ -64,7 +67,7 @@ export function ErrorDistribution({ distribution, title }: Props) {
distribution.bucketSize
);
- if (!buckets || distribution.noHits) {
+ if (!buckets) {
return (
bucket.y)) || 0;
const xMin = d3.min(buckets, (d) => d.x0);
const xMax = d3.max(buckets, (d) => d.x);
const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat();
@@ -84,6 +88,7 @@ export function ErrorDistribution({ distribution, title }: Props) {
{title}
bucket.x}
xType="time-utc"
@@ -105,6 +110,17 @@ export function ErrorDistribution({ distribution, title }: Props) {
values: { occCount: value },
})
}
+ legends={[
+ {
+ color: theme.euiColorVis1,
+ // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m
+ legendValue: numeral(averageValue).format('0a'),
+ title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', {
+ defaultMessage: 'Avg.',
+ }),
+ legendClickDisabled: true,
+ },
+ ]}
/>
);
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
index 0ad8a52b740d..b765dc42ede6 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
@@ -25,6 +25,9 @@ import { ErrorDistribution } from './Distribution';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../observability/public';
+import { callApmApi } from '../../../services/rest/createCallApmApi';
+import { ErrorRateChart } from '../../shared/charts/ErrorRateChart';
+import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';
const Titles = styled.div`
margin-bottom: ${px(units.plus)};
@@ -60,49 +63,43 @@ export function ErrorGroupDetails() {
const { urlParams, uiFilters } = useUrlParams();
const { serviceName, start, end, errorGroupId } = urlParams;
- const { data: errorGroupData } = useFetcher(
- (callApmApi) => {
- if (serviceName && start && end && errorGroupId) {
- return callApmApi({
- pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
- params: {
- path: {
- serviceName,
- groupId: errorGroupId,
- },
- query: {
- start,
- end,
- uiFilters: JSON.stringify(uiFilters),
- },
+ const { data: errorGroupData } = useFetcher(() => {
+ if (serviceName && start && end && errorGroupId) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
+ params: {
+ path: {
+ serviceName,
+ groupId: errorGroupId,
},
- });
- }
- },
- [serviceName, start, end, errorGroupId, uiFilters]
- );
-
- const { data: errorDistributionData } = useFetcher(
- (callApmApi) => {
- if (serviceName && start && end && errorGroupId) {
- return callApmApi({
- pathname: '/api/apm/services/{serviceName}/errors/distribution',
- params: {
- path: {
- serviceName,
- },
- query: {
- start,
- end,
- groupId: errorGroupId,
- uiFilters: JSON.stringify(uiFilters),
- },
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
},
- });
- }
- },
- [serviceName, start, end, errorGroupId, uiFilters]
- );
+ },
+ });
+ }
+ }, [serviceName, start, end, errorGroupId, uiFilters]);
+
+ const { data: errorDistributionData } = useFetcher(() => {
+ if (serviceName && start && end && errorGroupId) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/errors/distribution',
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ start,
+ end,
+ groupId: errorGroupId,
+ uiFilters: JSON.stringify(uiFilters),
+ },
+ },
+ });
+ }
+ }, [serviceName, start, end, errorGroupId, uiFilters]);
useTrackPageview({ app: 'apm', path: 'error_group_details' });
useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 });
@@ -184,16 +181,24 @@ export function ErrorGroupDetails() {
)}
-
-
+
+
+
+
+
+
+
+
+
+
{showDetails && (
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx
index ff031c5a86d1..73474208e26c 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx
@@ -13,64 +13,61 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
-import { useFetcher } from '../../../hooks/useFetcher';
-import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
-import { ErrorGroupList } from './List';
-import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../observability/public';
import { PROJECTION } from '../../../../common/projections/typings';
+import { useFetcher } from '../../../hooks/useFetcher';
+import { useUrlParams } from '../../../hooks/useUrlParams';
+import { callApmApi } from '../../../services/rest/createCallApmApi';
+import { ErrorRateChart } from '../../shared/charts/ErrorRateChart';
import { LocalUIFilters } from '../../shared/LocalUIFilters';
+import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
+import { ErrorGroupList } from './List';
+import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';
const ErrorGroupOverview: React.FC = () => {
const { urlParams, uiFilters } = useUrlParams();
const { serviceName, start, end, sortField, sortDirection } = urlParams;
- const { data: errorDistributionData } = useFetcher(
- (callApmApi) => {
- if (serviceName && start && end) {
- return callApmApi({
- pathname: '/api/apm/services/{serviceName}/errors/distribution',
- params: {
- path: {
- serviceName,
- },
- query: {
- start,
- end,
- uiFilters: JSON.stringify(uiFilters),
- },
+ const { data: errorDistributionData } = useFetcher(() => {
+ if (serviceName && start && end) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/errors/distribution',
+ params: {
+ path: {
+ serviceName,
},
- });
- }
- },
- [serviceName, start, end, uiFilters]
- );
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ },
+ },
+ });
+ }
+ }, [serviceName, start, end, uiFilters]);
- const { data: errorGroupListData } = useFetcher(
- (callApmApi) => {
- const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc';
+ const { data: errorGroupListData } = useFetcher(() => {
+ const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc';
- if (serviceName && start && end) {
- return callApmApi({
- pathname: '/api/apm/services/{serviceName}/errors',
- params: {
- path: {
- serviceName,
- },
- query: {
- start,
- end,
- sortField,
- sortDirection: normalizedSortDirection,
- uiFilters: JSON.stringify(uiFilters),
- },
+ if (serviceName && start && end) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/errors',
+ params: {
+ path: {
+ serviceName,
},
- });
- }
- },
- [serviceName, start, end, sortField, sortDirection, uiFilters]
- );
+ query: {
+ start,
+ end,
+ sortField,
+ sortDirection: normalizedSortDirection,
+ uiFilters: JSON.stringify(uiFilters),
+ },
+ },
+ });
+ }
+ }, [serviceName, start, end, sortField, sortDirection, uiFilters]);
useTrackPageview({
app: 'apm',
@@ -102,20 +99,27 @@ const ErrorGroupOverview: React.FC = () => {
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
index 7d37d2d67269..2c4cc185dac7 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js
@@ -108,7 +108,9 @@ export default function Legends({
return (
clickLegend(i)}
+ onClick={
+ serie.legendClickDisabled ? undefined : () => clickLegend(i)
+ }
disabled={seriesEnabledState[i]}
text={text}
color={serie.color}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js
index e1ffec3a8d97..7e74961e57ea 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js
@@ -144,7 +144,7 @@ export class InnerCustomPlot extends PureComponent {
const hasValidCoordinates = flatten(series.map((s) => s.data)).some((p) =>
isValidCoordinateValue(p.y)
);
- const noHits = !hasValidCoordinates;
+ const noHits = this.props.noHits || !hasValidCoordinates;
const plotValues = this.getPlotValues({
visibleSeries,
@@ -234,6 +234,7 @@ InnerCustomPlot.propTypes = {
firstSeen: PropTypes.number,
})
),
+ noHits: PropTypes.bool,
};
InnerCustomPlot.defaultProps = {
@@ -241,6 +242,8 @@ InnerCustomPlot.defaultProps = {
tickFormatX: undefined,
tickFormatY: (y) => y,
truncateLegends: false,
+ xAxisTickSizeOuter: 0,
+ noHits: false,
};
export default makeWidthFlexible(InnerCustomPlot);
diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
new file mode 100644
index 000000000000..7aafa9e1fdce
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
@@ -0,0 +1,100 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { EuiTitle } from '@elastic/eui';
+import theme from '@elastic/eui/dist/eui_theme_light.json';
+import { i18n } from '@kbn/i18n';
+import mean from 'lodash.mean';
+import React, { useCallback } from 'react';
+import { useChartsSync } from '../../../../hooks/useChartsSync';
+import { useFetcher } from '../../../../hooks/useFetcher';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { callApmApi } from '../../../../services/rest/createCallApmApi';
+import { unit } from '../../../../style/variables';
+import { asPercent } from '../../../../utils/formatters';
+// @ts-ignore
+import CustomPlot from '../CustomPlot';
+
+const tickFormatY = (y?: number) => {
+ return asPercent(y || 0, 1);
+};
+
+export const ErrorRateChart = () => {
+ const { urlParams, uiFilters } = useUrlParams();
+ const syncedChartsProps = useChartsSync();
+
+ const { serviceName, start, end, errorGroupId } = urlParams;
+ const { data: errorRateData } = useFetcher(() => {
+ if (serviceName && start && end) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/errors/rate',
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ groupId: errorGroupId,
+ },
+ },
+ });
+ }
+ }, [serviceName, start, end, uiFilters, errorGroupId]);
+
+ const combinedOnHover = useCallback(
+ (hoverX: number) => {
+ return syncedChartsProps.onHover(hoverX);
+ },
+ [syncedChartsProps]
+ );
+
+ const errorRates = errorRateData?.errorRates || [];
+
+ return (
+ <>
+
+
+ {i18n.translate('xpack.apm.errorRateChart.title', {
+ defaultMessage: 'Error Rate',
+ })}
+
+
+ rate.y))),
+ legendClickDisabled: true,
+ title: i18n.translate('xpack.apm.errorRateChart.avgLabel', {
+ defaultMessage: 'Avg.',
+ }),
+ type: 'linemark',
+ hideTooltipValue: true,
+ },
+ {
+ data: errorRates,
+ type: 'line',
+ color: theme.euiColorVis7,
+ hideLegend: true,
+ title: i18n.translate('xpack.apm.errorRateChart.rateLabel', {
+ defaultMessage: 'Rate',
+ }),
+ },
+ ]}
+ onHover={combinedOnHover}
+ tickFormatY={tickFormatY}
+ formatTooltipValue={({ y }: { y?: number }) =>
+ Number.isFinite(y) ? tickFormatY(y) : 'N/A'
+ }
+ height={unit * 10}
+ />
+ >
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap
index 1f935af7c899..a31b9735628a 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap
+++ b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap
@@ -114,7 +114,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
0 ms
@@ -149,7 +149,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
500 ms
@@ -184,7 +184,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
1,000 ms
@@ -219,7 +219,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
1,500 ms
@@ -254,7 +254,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
2,000 ms
@@ -289,7 +289,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
2,500 ms
@@ -324,7 +324,7 @@ exports[`Histogram Initially should have default markup 1`] = `
x1={0}
x2={0}
y1={-0}
- y2={10}
+ y2={0}
/>
3,000 ms
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js
index 4eca1a37c51b..002ff19d0d1d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js
+++ b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js
@@ -26,6 +26,10 @@ import Tooltip from '../Tooltip';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { tint } from 'polished';
import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone';
+import Legends from '../CustomPlot/Legends';
+import StatusText from '../CustomPlot/StatusText';
+import { i18n } from '@kbn/i18n';
+import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
const XY_HEIGHT = unit * 10;
const XY_MARGIN = {
@@ -99,6 +103,7 @@ export class HistogramInner extends PureComponent {
tooltipHeader,
verticalLineHover,
width: XY_WIDTH,
+ legends,
} = this.props;
const { hoveredBucket } = this.state;
if (isEmpty(buckets) || XY_WIDTH === 0) {
@@ -139,102 +144,140 @@ export class HistogramInner extends PureComponent {
const showVerticalLineHover = verticalLineHover(hoveredBucket);
const showBackgroundHover = backgroundHover(hoveredBucket);
+ const hasValidCoordinates = buckets.some((bucket) =>
+ isValidCoordinateValue(bucket.y)
+ );
+ const noHits = this.props.noHits || !hasValidCoordinates;
+
+ const xyPlotProps = {
+ dontCheckIfEmpty: true,
+ xType: this.props.xType,
+ width: XY_WIDTH,
+ height: XY_HEIGHT,
+ margin: XY_MARGIN,
+ xDomain: xDomain,
+ yDomain: yDomain,
+ };
+
+ const xAxisProps = {
+ style: { strokeWidth: '1px' },
+ marginRight: 10,
+ tickSize: 0,
+ tickTotal: X_TICK_TOTAL,
+ tickFormat: formatX,
+ tickValues: xTickValues,
+ };
+
+ const emptyStateChart = (
+
+
+
+
+ );
+
return (
-
-
-
-
-
- {showBackgroundHover && (
-
- )}
-
- {shouldShowTooltip && (
-
- )}
-
- {selectedBucket && (
-
- )}
-
-
-
- {showVerticalLineHover && (
-
- )}
-
- {
- return {
- ...bucket,
- xCenter: (bucket.x0 + bucket.x) / 2,
- };
- })}
- onClick={this.onClick}
- onHover={this.onHover}
- onBlur={this.onBlur}
- x={(d) => x(d.xCenter)}
- y={() => 1}
- />
-
+ {noHits ? (
+ <>{emptyStateChart}>
+ ) : (
+ <>
+
+
+
+
+
+ {showBackgroundHover && (
+
+ )}
+
+ {shouldShowTooltip && (
+
+ )}
+
+ {selectedBucket && (
+
+ )}
+
+
+
+ {showVerticalLineHover && hoveredBucket?.x && (
+
+ )}
+
+ {
+ return {
+ ...bucket,
+ xCenter: (bucket.x0 + bucket.x) / 2,
+ };
+ })}
+ onClick={this.onClick}
+ onHover={this.onHover}
+ onBlur={this.onBlur}
+ x={(d) => x(d.xCenter)}
+ y={() => 1}
+ />
+
+
+ {legends && (
+ {}}
+ truncateLegends={false}
+ noHits={noHits}
+ />
+ )}
+ >
+ )}
);
@@ -255,6 +298,8 @@ HistogramInner.propTypes = {
verticalLineHover: PropTypes.func,
width: PropTypes.number.isRequired,
xType: PropTypes.string,
+ legends: PropTypes.array,
+ noHits: PropTypes.bool,
};
HistogramInner.defaultProps = {
@@ -265,6 +310,7 @@ HistogramInner.defaultProps = {
tooltipHeader: () => null,
verticalLineHover: () => null,
xType: 'linear',
+ noHits: false,
};
export default makeWidthFlexible(HistogramInner);
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
new file mode 100644
index 000000000000..d558e3942a42
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 {
+ ERROR_GROUP_ID,
+ PROCESSOR_EVENT,
+ SERVICE_NAME,
+} from '../../../common/elasticsearch_fieldnames';
+import { ProcessorEvent } from '../../../common/processor_event';
+import { getMetricsDateHistogramParams } from '../helpers/metrics';
+import { rangeFilter } from '../helpers/range_filter';
+import {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+} from '../helpers/setup_request';
+
+export async function getErrorRate({
+ serviceName,
+ groupId,
+ setup,
+}: {
+ serviceName: string;
+ groupId?: string;
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+}) {
+ const { start, end, uiFiltersES, client, indices } = setup;
+
+ const filter = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { range: rangeFilter(start, end) },
+ ...uiFiltersES,
+ ];
+
+ const aggs = {
+ response_times: {
+ date_histogram: getMetricsDateHistogramParams(start, end),
+ },
+ };
+
+ const getTransactionBucketAggregation = async () => {
+ const resp = await client.search({
+ index: indices['apm_oss.transactionIndices'],
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ ...filter,
+ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
+ ],
+ },
+ },
+ aggs,
+ },
+ });
+ return {
+ totalHits: resp.hits.total.value,
+ responseTimeBuckets: resp.aggregations?.response_times.buckets,
+ };
+ };
+ const getErrorBucketAggregation = async () => {
+ const groupIdFilter = groupId
+ ? [{ term: { [ERROR_GROUP_ID]: groupId } }]
+ : [];
+ const resp = await client.search({
+ index: indices['apm_oss.errorIndices'],
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ ...filter,
+ ...groupIdFilter,
+ { term: { [PROCESSOR_EVENT]: ProcessorEvent.error } },
+ ],
+ },
+ },
+ aggs,
+ },
+ });
+ return resp.aggregations?.response_times.buckets;
+ };
+
+ const [transactions, errorResponseTimeBuckets] = await Promise.all([
+ getTransactionBucketAggregation(),
+ getErrorBucketAggregation(),
+ ]);
+
+ const transactionCountByTimestamp: Record = {};
+ if (transactions?.responseTimeBuckets) {
+ transactions.responseTimeBuckets.forEach((bucket) => {
+ transactionCountByTimestamp[bucket.key] = bucket.doc_count;
+ });
+ }
+
+ const errorRates = errorResponseTimeBuckets?.map((bucket) => {
+ const { key, doc_count: errorCount } = bucket;
+ const relativeRate = errorCount / transactionCountByTimestamp[key];
+ return { x: key, y: relativeRate };
+ });
+
+ return {
+ noHits: transactions?.totalHits === 0,
+ errorRates,
+ };
+}
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index 774f1f27435a..bdfb49fa3082 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -13,6 +13,7 @@ import {
errorDistributionRoute,
errorGroupsRoute,
errorsRoute,
+ errorRateRoute,
} from './errors';
import {
serviceAgentNameRoute,
@@ -81,6 +82,7 @@ const createApmApi = () => {
.add(errorDistributionRoute)
.add(errorGroupsRoute)
.add(errorsRoute)
+ .add(errorRateRoute)
// Services
.add(serviceAgentNameRoute)
diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts
index 1615550027d3..97314a9a6166 100644
--- a/x-pack/plugins/apm/server/routes/errors.ts
+++ b/x-pack/plugins/apm/server/routes/errors.ts
@@ -11,6 +11,7 @@ import { getErrorGroup } from '../lib/errors/get_error_group';
import { getErrorGroups } from '../lib/errors/get_error_groups';
import { setupRequest } from '../lib/helpers/setup_request';
import { uiFiltersRt, rangeRt } from './default_api_types';
+import { getErrorRate } from '../lib/errors/get_error_rate';
export const errorsRoute = createRoute(() => ({
path: '/api/apm/services/{serviceName}/errors',
@@ -80,3 +81,26 @@ export const errorDistributionRoute = createRoute(() => ({
return getErrorDistribution({ serviceName, groupId, setup });
},
}));
+
+export const errorRateRoute = createRoute(() => ({
+ path: '/api/apm/services/{serviceName}/errors/rate',
+ params: {
+ path: t.type({
+ serviceName: t.string,
+ }),
+ query: t.intersection([
+ t.partial({
+ groupId: t.string,
+ }),
+ uiFiltersRt,
+ rangeRt,
+ ]),
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ const { params } = context;
+ const { serviceName } = params.path;
+ const { groupId } = params.query;
+ return getErrorRate({ serviceName, groupId, setup });
+ },
+}));
From 8d5934f2578ee7b713e3bc2aaa1901b821a43bb7 Mon Sep 17 00:00:00 2001
From: Stacey Gammon
Date: Tue, 23 Jun 2020 06:59:45 -0400
Subject: [PATCH 30/33] Embed documentation on input and output state (#69443)
(#69529)
* Embed documentation on input and output state
* json -> js
* Add section on how id is used by custom time range badge action to determine isCompatible
---
src/plugins/embeddable/docs/README.md | 3 +-
.../docs/containers_and_inherited_state.md | 2 +-
.../embeddable/docs/input_and_output_state.md | 282 ++++++++++++++++++
3 files changed, 285 insertions(+), 2 deletions(-)
create mode 100644 src/plugins/embeddable/docs/input_and_output_state.md
diff --git a/src/plugins/embeddable/docs/README.md b/src/plugins/embeddable/docs/README.md
index 1b6c7be13b1d..ce5e76d54a04 100644
--- a/src/plugins/embeddable/docs/README.md
+++ b/src/plugins/embeddable/docs/README.md
@@ -2,4 +2,5 @@
## Reference
-- [Embeddable containers and inherited input state](./containers_and_inherited_state.md)
+- [Input and output state](./input_and_output_state.md)
+- [Common mistakes with embeddable containers and inherited input state](./containers_and_inherited_state.md)
diff --git a/src/plugins/embeddable/docs/containers_and_inherited_state.md b/src/plugins/embeddable/docs/containers_and_inherited_state.md
index c950bef96002..35e399f89c13 100644
--- a/src/plugins/embeddable/docs/containers_and_inherited_state.md
+++ b/src/plugins/embeddable/docs/containers_and_inherited_state.md
@@ -1,4 +1,4 @@
-## Embeddable containers and inherited input state
+## Common mistakes with embeddable containers and inherited input state
`updateInput` is typed as `updateInput(input: Partial)`. Notice it's _partial_. This is to support the use case of inherited state when an embeddable is inside a container.
diff --git a/src/plugins/embeddable/docs/input_and_output_state.md b/src/plugins/embeddable/docs/input_and_output_state.md
new file mode 100644
index 000000000000..810dc72664f9
--- /dev/null
+++ b/src/plugins/embeddable/docs/input_and_output_state.md
@@ -0,0 +1,282 @@
+## Input and output state
+
+### What's the difference?
+
+Input vs Output State
+
+| Input | Output |
+| ----------- | ----------- |
+| Public, on the IEmbeddable interface. `embeddable.updateInput(changedInput)` | Protected inside the Embeddable class. `this.updateOutput(changedOutput)` |
+| Serializable representation of the embeddable | Does not need to be serializable |
+| Can be updated throughout the lifecycle of an Embeddable | Often derived from input state |
+
+Non-real examples to showcase the difference:
+
+| Input | Output |
+| ----------- | ----------- |
+| savedObjectId | savedObjectAttributes |
+| esQueryRequest | esQueryResponse |
+| props | renderComplete |
+
+### Types of input state
+
+#### Inherited input state
+
+The only reason we have different types of input state is to support embeddable containers, and children embeddables _inheriting_ state from the container.
+For example, when the dashboard time range changes, so does
+the time range of all children embeddables. Dashboard passes down time range as _inherited_ input state. From the viewpoint of the child Embeddable,
+time range is just input state. It doesn't care where it gets this data from.
+
+
+For example, imagine a container with this input:
+
+```js
+{
+ gridData: {...},
+ timeRange: 'now-15m to now',
+
+ // Every embeddable container has a panels mapping. It's how the base container class manages common changes like children being
+ // added, removed or edited.
+ panels: {
+ ['1']: {
+ // `type` is used to grab the right embeddable factory. Every PanelState must specify one.
+ type: 'clock',
+
+ // `explicitInput` is combined with `inheritedInput` to create `childInput`, and is use like:
+ // `embeddableFactories.get(type).create(childInput)`.
+ explicitInput: {
+
+ // All explicitInput is required to have an id. This is used as a way for the
+ // embeddable to know where it exists in the panels array if it's living in a container.
+ // Note, this is NOT THE SAVED OBJECT ID! Even though it's sometimes used to store the saved object id.
+ id: '1',
+ }
+ }
+ }
+}
+```
+
+That could result in the following input being passed to a child:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ id: '1',
+}
+```
+
+Notice that `gridData` is not passed down, but `timeRange` is. What ends up as _inherited_ state, that is passed down to a child, is up to the specific
+implementation of a container and
+determined by the abstract function `Container.getInheritedInput()`
+
+#### Overridding inherited input
+
+We wanted to support _overriding_ this inherited state, to support the "Per panel time range" feature. The _inherited_ `timeRange` input can be
+overridden by the _explicit_ `timeRange` input.
+
+Take this example dashboard container input:
+
+```js
+{
+ gridData: {...},
+ timeRange: 'now-15m to now',
+ panels: {
+ ['1']: {
+ type: 'clock',
+ explicitInput: {
+ timeRange: 'now-30m to now',
+ id: '1',
+ }
+ },
+ ['2']: {
+ type: 'clock',
+ explicitInput: {
+ id: '2',
+ }
+ },
+}
+```
+
+The first child embeddable will get passed input state:
+
+```js
+{
+ timeRange: 'now-30m to now',
+ id: '1',
+}
+```
+
+This override wouldn't affect other children, so the second child would receive:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ id: '2',
+}
+```
+
+#### EmbeddableInput.id and some technical debt
+
+Above I said:
+
+> From the viewpoint of the child Embeddable,
+> time range is just input state. It doesn't care where it gets this data from.
+
+and this is mostly true, however, the primary reason EmbeddableInput.id exists is to support the
+case where the custom time range badge action needs to look up a child's explicit input on the
+parent. It does this to determine whether or not to show the badge. The logic is something like:
+
+```ts
+ // If there is no explicit input defined on the parent then this embeddable inherits the
+ // time range from whatever the time range of the parent is.
+ return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined;
+```
+
+It doesn't just compare the timeRange input on the parent (`embeddable.parent?.getInput().timeRange` )because even if they happen to match,
+we still want the badge showing to indicate the time range is "locked" on this particular panel.
+
+Note that `parent` can be retrieved from either `embeddabble.parent` or `embeddable.getRoot()`. The
+`getRoot` variety will walk up to find the root parent, even though we have no tested or used
+nested containers, it is theoretically possible.
+
+This EmbeddableInput.id parameter is marked as required on the `EmbeddableInput` interface, even though it's only used
+when an embeddable is inside a parent. There is also no
+typescript safety to ensure the id matches the panel id in the parents json:
+
+```js
+ ['2']: {
+ type: 'clock',
+ explicitInput: {
+ id: '3', // No! Should be 2!
+ }
+ },
+```
+
+It should probably be something that the parent passes down to the child specifically, based on the panel mapping key,
+and renamed to something like `panelKeyInParent`.
+
+Note that this has nothing to do with a saved object id, even though in dashboard app, the saved object happens to be
+used as the dashboard container id. Another reason this should probably not be required for embeddables not
+inside containers.
+
+#### A container can pass down any information to the children
+
+It doesn't have to be part of it's own input. It's possible for a container input like:
+
+
+```js
+{
+ timeRange: 'now-15m to now',
+ panels: {
+ ['1']: {
+ type: 'clock',
+ explicitInput: {
+ timeRange: 'now-30m to now',
+ id: '1',
+ }
+ }
+}
+```
+
+to pass down this input:
+
+```js
+{
+ timeRange: 'now-30m to now',
+ id: '1',
+ zed: 'bar', // <-- Where did this come from??
+}
+```
+
+I don't have a realistic use case for this, just noting it's possible in any containers implementation of `getInheritedInput`. Note this is still considered
+inherited input because it's coming from the container.
+
+#### Explicit input stored on behalf of the container
+
+It's possible for a container to store explicit input state on behalf of an embeddable, without knowing what that state is. For example, a container could
+have input state like:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ panels: {
+ ['1']: {
+ type: 'clock',
+ explicitInput: {
+ display: 'analog',
+ id: '1',
+ }
+ }
+}
+```
+
+And what gets passed to the child is:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ id: '1',
+ display: 'analog'
+}
+```
+
+even if a container has no idea about this `clock` embeddable implementation, nor this `explicitInput.display` field.
+
+There are two ways for this kind of state to end up in `panels[id].explicitInput`.
+
+1. `ClockEmbeddableFactory.getExplicitInput` returns it.
+2. `ClockEmbeddableFactory.getDefaultInput` returns it. (This function is largely unused. We may be able to get rid of it.)
+3. Someone called `embeddable.updateInput({ display: 'analog' })`, when the embeddable is a child in a container.
+
+#### Containers can pass down too much information
+
+Lets say our container state is:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ panels: {
+ ['1']: {
+ type: 'helloWorld',
+ explicitInput: {
+ id: '1',
+ }
+ }
+}
+```
+
+What gets passed to the child is:
+
+```js
+{
+ timeRange: 'now-15m to now',
+ id: '1',
+}
+```
+
+It doesn't matter if the embeddable does not require, nor use, `timeRange`. The container passes down inherited input state to every child.
+This could present problems with trying to figure out which embeddables support
+different types of actions. For example, it'd be great if "Customize time range" action only showed up on embeddables that actually did something
+with the `timeRange`. You can't check at runtime whether `input.timeRange === undefined` to do so though, because it will be passed in by the container
+regardless.
+
+
+#### Tech debt warnings
+
+`EmbeddableFactory.getExplicitInput` was intended as a way for an embeddable to retrieve input state it needs, that will not
+be provided by a container. However, an embeddable won't know where it will be rendered, so how will the factory know which
+required data to ask from the user and which will be inherited from the container? I believe `getDefaultInput` was meant to solve this.
+`getDefaultInput` would provide default values, only if the container didn't supply them through inheritance. Explicit input would
+always provide these values, and would always be stored in a containers `panel[id].explicitInput`, even if the container _did_ provide
+them.
+
+There are no real life examples showcasing this, it may not even be really needed by current use cases. Containers were built as an abstraction, with
+the thinking being that it would support any type of rendering of child embeddables - whether in a "snap to grid" style like dashboard,
+or in a free form layout like canvas.
+
+The only real implementation of a container in production code at the time this is written is Dashboard however, with no plans to migrate
+Canvas over to use it (this was the original impetus for an abstraction). The container code is quite complicated with child management,
+so it makes creating a new container very easy, as you can see in the developer examples of containers. But, it's possible this layer was
+ an over abstraction without a real prod use case (I can say that because I wrote it, I'm only insulting myself!) :).
+
+Be sure to read [Common mistakes with embeddable containers and inherited input state](./containers_and_inherited_state.md) next!
\ No newline at end of file
From f8ccb03395eb06337fa87d8b62a5f9547bb7e035 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 23 Jun 2020 09:02:27 -0400
Subject: [PATCH 31/33] [7.x] [IM] Move template wizard steps to shared
directory + update legacy details panel (#69559) (#69670)
---
.../helpers/test_subjects.ts | 6 +--
.../home/index_templates_tab.test.ts | 12 +++---
.../components/shared/components/index.ts | 2 +
.../shared/components/wizard_steps/index.ts | 9 ++++
.../components/wizard_steps}/step_aliases.tsx | 22 +++++-----
.../wizard_steps}/step_mappings.tsx | 16 ++++----
.../wizard_steps}/step_settings.tsx | 20 ++++-----
.../components/wizard_steps}/use_json_step.ts | 2 +-
.../application/components/shared/index.ts | 9 +++-
.../steps/step_aliases_container.tsx | 11 ++++-
.../steps/step_mappings_container.tsx | 4 +-
.../steps/step_settings_container.tsx | 11 ++++-
.../template_details/template_details.tsx | 29 +++++++------
.../template_details/tabs/index.ts | 3 --
.../template_details/tabs/tab_aliases.tsx | 41 -------------------
.../template_details/tabs/tab_mappings.tsx | 41 -------------------
.../template_details/tabs/tab_settings.tsx | 41 -------------------
.../application/services/documentation.ts | 4 ++
.../translations/translations/ja-JP.json | 18 --------
.../translations/translations/zh-CN.json | 18 --------
20 files changed, 99 insertions(+), 220 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_aliases.tsx (80%)
rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_mappings.tsx (84%)
rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_settings.tsx (81%)
rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/use_json_step.ts (96%)
delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
index 4e297118b0fd..9889ebe16ba1 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
@@ -5,7 +5,7 @@
*/
export type TestSubjects =
- | 'aliasesTab'
+ | 'aliasesTabContent'
| 'appTitle'
| 'cell'
| 'closeDetailsButton'
@@ -27,7 +27,7 @@ export type TestSubjects =
| 'indicesTab'
| 'legacyTemplateTable'
| 'manageTemplateButton'
- | 'mappingsTab'
+ | 'mappingsTabContent'
| 'noAliasesCallout'
| 'noMappingsCallout'
| 'noSettingsCallout'
@@ -36,7 +36,7 @@ export type TestSubjects =
| 'row'
| 'sectionError'
| 'sectionLoading'
- | 'settingsTab'
+ | 'settingsTabContent'
| 'summaryTab'
| 'summaryTitle'
| 'systemTemplatesSwitch'
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index 7c79c7e61174..2ff3743cd866 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -432,18 +432,18 @@ describe('Index Templates tab', () => {
// Navigate and verify all tabs
actions.selectDetailsTab('settings');
expect(exists('summaryTab')).toBe(false);
- expect(exists('settingsTab')).toBe(true);
+ expect(exists('settingsTabContent')).toBe(true);
actions.selectDetailsTab('aliases');
expect(exists('summaryTab')).toBe(false);
- expect(exists('settingsTab')).toBe(false);
- expect(exists('aliasesTab')).toBe(true);
+ expect(exists('settingsTabContent')).toBe(false);
+ expect(exists('aliasesTabContent')).toBe(true);
actions.selectDetailsTab('mappings');
expect(exists('summaryTab')).toBe(false);
- expect(exists('settingsTab')).toBe(false);
- expect(exists('aliasesTab')).toBe(false);
- expect(exists('mappingsTab')).toBe(true);
+ expect(exists('settingsTabContent')).toBe(false);
+ expect(exists('aliasesTabContent')).toBe(false);
+ expect(exists('mappingsTabContent')).toBe(true);
});
test('should show an info callout if data is not present', async () => {
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
index 90d66bd1a5da..e1700ad6a632 100644
--- a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
@@ -5,3 +5,5 @@
*/
export { TabAliases, TabMappings, TabSettings } from './details_panel';
+
+export { StepAliases, StepMappings, StepSettings } from './wizard_steps';
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
new file mode 100644
index 000000000000..90ce6227c09c
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { StepAliases } from './step_aliases';
+export { StepMappings } from './step_mappings';
+export { StepSettings } from './step_settings';
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx
similarity index 80%
rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx
index e18846a69b84..0d28ec4b50c9 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx
@@ -19,17 +19,17 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
+import { Forms } from '../../../../../shared_imports';
import { useJsonStep } from './use_json_step';
interface Props {
defaultValue: { [key: string]: any };
onChange: (content: Forms.Content) => void;
+ esDocsBase: string;
}
export const StepAliases: React.FunctionComponent = React.memo(
- ({ defaultValue, onChange }) => {
+ ({ defaultValue, onChange, esDocsBase }) => {
const { jsonContent, setJsonContent, error } = useJsonStep({
defaultValue,
onChange,
@@ -42,7 +42,7 @@ export const StepAliases: React.FunctionComponent = React.memo(
@@ -53,7 +53,7 @@ export const StepAliases: React.FunctionComponent = React.memo(
@@ -64,13 +64,13 @@ export const StepAliases: React.FunctionComponent = React.memo(
@@ -82,13 +82,13 @@ export const StepAliases: React.FunctionComponent = React.memo(
}
helpText={
= React.memo(
showGutter={false}
minLines={6}
aria-label={i18n.translate(
- 'xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel',
+ 'xpack.idxMgmt.formWizard.stepAliases.fieldAliasesAriaLabel',
{
defaultMessage: 'Aliases code editor',
}
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx
similarity index 84%
rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx
index 800cb519a939..2b9b689e17cb 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx
@@ -15,23 +15,23 @@ import {
EuiText,
} from '@elastic/eui';
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
+import { Forms } from '../../../../../shared_imports';
import {
MappingsEditor,
OnUpdateHandler,
LoadMappingsFromJsonButton,
IndexSettings,
-} from '../../mappings_editor';
+} from '../../../mappings_editor';
interface Props {
defaultValue: { [key: string]: any };
onChange: (content: Forms.Content) => void;
indexSettings?: IndexSettings;
+ esDocsBase: string;
}
export const StepMappings: React.FunctionComponent = React.memo(
- ({ defaultValue, onChange, indexSettings }) => {
+ ({ defaultValue, onChange, indexSettings, esDocsBase }) => {
const [mappings, setMappings] = useState(defaultValue);
const onMappingsEditorUpdate = useCallback(
@@ -58,7 +58,7 @@ export const StepMappings: React.FunctionComponent = React.memo(
@@ -69,7 +69,7 @@ export const StepMappings: React.FunctionComponent = React.memo(
@@ -86,12 +86,12 @@ export const StepMappings: React.FunctionComponent = React.memo(
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx
similarity index 81%
rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx
index 4325852d68aa..4eafcee0ef51 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx
@@ -19,17 +19,17 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
+import { Forms } from '../../../../../shared_imports';
import { useJsonStep } from './use_json_step';
interface Props {
defaultValue: { [key: string]: any };
onChange: (content: Forms.Content) => void;
+ esDocsBase: string;
}
export const StepSettings: React.FunctionComponent = React.memo(
- ({ defaultValue, onChange }) => {
+ ({ defaultValue, onChange, esDocsBase }) => {
const { jsonContent, setJsonContent, error } = useJsonStep({
defaultValue,
onChange,
@@ -42,7 +42,7 @@ export const StepSettings: React.FunctionComponent = React.memo(
@@ -53,7 +53,7 @@ export const StepSettings: React.FunctionComponent = React.memo(
@@ -64,12 +64,12 @@ export const StepSettings: React.FunctionComponent = React.memo(
@@ -82,13 +82,13 @@ export const StepSettings: React.FunctionComponent = React.memo(
}
helpText={
{JSON.stringify({ number_of_replicas: 1 })},
@@ -114,7 +114,7 @@ export const StepSettings: React.FunctionComponent = React.memo(
showGutter={false}
minLines={6}
aria-label={i18n.translate(
- 'xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel',
+ 'xpack.idxMgmt.formWizard.stepSettings.fieldIndexSettingsAriaLabel',
{
defaultMessage: 'Index settings editor',
}
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts
similarity index 96%
rename from x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts
index 4c1b36e3abba..67799f1e76d8 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts
@@ -7,7 +7,7 @@
import { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import { isJSON, Forms } from '../../../../shared_imports';
+import { isJSON, Forms } from '../../../../../shared_imports';
interface Parameters {
onChange: (content: Forms.Content) => void;
diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts
index e015ef72e244..5ec1f7171027 100644
--- a/x-pack/plugins/index_management/public/application/components/shared/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/index.ts
@@ -4,4 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { TabAliases, TabMappings, TabSettings } from './components';
+export {
+ TabAliases,
+ TabMappings,
+ TabSettings,
+ StepAliases,
+ StepMappings,
+ StepSettings,
+} from './components';
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
index 634887436f81..a0e0c59be662 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
@@ -6,11 +6,18 @@
import React from 'react';
import { Forms } from '../../../../shared_imports';
+import { documentationService } from '../../../services/documentation';
+import { StepAliases } from '../../shared';
import { WizardContent } from '../template_form';
-import { StepAliases } from './step_aliases';
export const StepAliasesContainer = () => {
const { defaultValue, updateContent } = Forms.useContent('aliases');
- return ;
+ return (
+
+ );
};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx
index 545aec985159..80c0d1d4df48 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx
@@ -6,8 +6,9 @@
import React from 'react';
import { Forms } from '../../../../shared_imports';
+import { documentationService } from '../../../services/documentation';
+import { StepMappings } from '../../shared';
import { WizardContent } from '../template_form';
-import { StepMappings } from './step_mappings';
export const StepMappingsContainer = () => {
const { defaultValue, updateContent, getData } = Forms.useContent('mappings');
@@ -17,6 +18,7 @@ export const StepMappingsContainer = () => {
defaultValue={defaultValue}
onChange={updateContent}
indexSettings={getData().settings}
+ esDocsBase={documentationService.getEsDocsBase()}
/>
);
};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
index 4d7de644a144..b79c6804d382 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
@@ -6,11 +6,18 @@
import React from 'react';
import { Forms } from '../../../../shared_imports';
+import { documentationService } from '../../../services/documentation';
+import { StepSettings } from '../../shared';
import { WizardContent } from '../template_form';
-import { StepSettings } from './step_settings';
export const StepSettingsContainer = React.memo(() => {
const { defaultValue, updateContent } = Forms.useContent('settings');
- return ;
+ return (
+
+ );
});
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
index 807229fb3626..ab4ce6a61a9b 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
@@ -30,7 +30,6 @@ import {
UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
} from '../../../../../../../common/constants';
-import { TemplateDeserialized } from '../../../../../../../common';
import {
TemplateDeleteModal,
SectionLoading,
@@ -41,7 +40,8 @@ import { useLoadIndexTemplate } from '../../../../../services/api';
import { decodePathFromReactRouter } from '../../../../../services/routing';
import { SendRequestResponse } from '../../../../../../shared_imports';
import { useServices } from '../../../../../app_context';
-import { TabSummary, TabMappings, TabSettings, TabAliases } from '../../template_details/tabs';
+import { TabAliases, TabMappings, TabSettings } from '../../../../../components/shared';
+import { TabSummary } from '../../template_details/tabs';
interface Props {
template: { name: string; isLegacy?: boolean };
@@ -83,15 +83,6 @@ const TABS = [
},
];
-const tabToComponentMap: {
- [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>;
-} = {
- [SUMMARY_TAB_ID]: TabSummary,
- [SETTINGS_TAB_ID]: TabSettings,
- [MAPPINGS_TAB_ID]: TabMappings,
- [ALIASES_TAB_ID]: TabAliases,
-};
-
const tabToUiMetricMap: { [key: string]: string } = {
[SUMMARY_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB,
[SETTINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
@@ -144,7 +135,19 @@ export const LegacyTemplateDetails: React.FunctionComponent = ({
/>
);
} else if (templateDetails) {
- const Content = tabToComponentMap[activeTab];
+ const {
+ template: { settings, mappings, aliases },
+ } = templateDetails;
+
+ const tabToComponentMap: Record = {
+ [SUMMARY_TAB_ID]: ,
+ [SETTINGS_TAB_ID]: ,
+ [MAPPINGS_TAB_ID]: ,
+ [ALIASES_TAB_ID]: ,
+ };
+
+ const tabContent = tabToComponentMap[activeTab];
+
const managedTemplateCallout = isManaged ? (
= ({
-
+ {tabContent}
);
}
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts
index 7af28f4688f4..08ebda2b5e43 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts
@@ -5,6 +5,3 @@
*/
export { TabSummary } from './tab_summary';
-export { TabMappings } from './tab_mappings';
-export { TabSettings } from './tab_settings';
-export { TabAliases } from './tab_aliases';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
deleted file mode 100644
index fa7d734ad0d2..000000000000
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
+++ /dev/null
@@ -1,41 +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 React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { TemplateDeserialized } from '../../../../../../../common';
-
-interface Props {
- templateDetails: TemplateDeserialized;
-}
-
-export const TabAliases: React.FunctionComponent = ({ templateDetails }) => {
- const {
- template: { aliases },
- } = templateDetails;
-
- if (aliases && Object.keys(aliases).length) {
- return (
-
- {JSON.stringify(aliases, null, 2)}
-
- );
- }
-
- return (
-
- }
- iconType="pin"
- data-test-subj="noAliasesCallout"
- />
- );
-};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
deleted file mode 100644
index 6e0257c6b377..000000000000
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
+++ /dev/null
@@ -1,41 +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 React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { TemplateDeserialized } from '../../../../../../../common';
-
-interface Props {
- templateDetails: TemplateDeserialized;
-}
-
-export const TabMappings: React.FunctionComponent = ({ templateDetails }) => {
- const {
- template: { mappings },
- } = templateDetails;
-
- if (mappings && Object.keys(mappings).length) {
- return (
-
- {JSON.stringify(mappings, null, 2)}
-
- );
- }
-
- return (
-
- }
- iconType="pin"
- data-test-subj="noMappingsCallout"
- />
- );
-};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
deleted file mode 100644
index 8f75c2cb7780..000000000000
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
+++ /dev/null
@@ -1,41 +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 React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { TemplateDeserialized } from '../../../../../../../common';
-
-interface Props {
- templateDetails: TemplateDeserialized;
-}
-
-export const TabSettings: React.FunctionComponent = ({ templateDetails }) => {
- const {
- template: { settings },
- } = templateDetails;
-
- if (settings && Object.keys(settings).length) {
- return (
-
- {JSON.stringify(settings, null, 2)}
-
- );
- }
-
- return (
-
- }
- iconType="pin"
- data-test-subj="noSettingsCallout"
- />
- );
-};
diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts
index fdf07c43a0c8..ccccccce1976 100644
--- a/x-pack/plugins/index_management/public/application/services/documentation.ts
+++ b/x-pack/plugins/index_management/public/application/services/documentation.ts
@@ -20,6 +20,10 @@ class DocumentationService {
this.kibanaDocsBase = `${docsBase}/kibana/${DOC_LINK_VERSION}`;
}
+ public getEsDocsBase() {
+ return this.esDocsBase;
+ }
+
public getSettingsDocumentationLink() {
return `${this.esDocsBase}/index-modules.html#index-modules-settings`;
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index dd15a9925b4d..c44d08975182 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7072,9 +7072,6 @@
"xpack.idxMgmt.summary.summaryTitle": "一般",
"xpack.idxMgmt.templateCreate.loadingTemplateToCloneDescription": "クローンを作成するテンプレートを読み込み中…",
"xpack.idxMgmt.templateCreate.loadingTemplateToCloneErrorMessage": "クローンを作成するテンプレートを読み込み中にエラーが発生",
- "xpack.idxMgmt.templateDetails.aliasesTab.noAliasesTitle": "エイリアスが定義されていません。",
- "xpack.idxMgmt.templateDetails.mappingsTab.noMappingsTitle": "マッピングが定義されていません。",
- "xpack.idxMgmt.templateDetails.settingsTab.noSettingsTitle": "設定が定義されていません。",
"xpack.idxMgmt.templateDetails.summaryTab.ilmPolicyDescriptionListTitle": "ILM ポリシー",
"xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "インデックス{numIndexPatterns, plural, one {パターン} other {パターン}}",
"xpack.idxMgmt.templateDetails.summaryTab.noneDescriptionText": "なし",
@@ -7089,12 +7086,6 @@
"xpack.idxMgmt.templateForm.createButtonLabel": "テンプレートを作成",
"xpack.idxMgmt.templateForm.saveButtonLabel": "テンプレートを保存",
"xpack.idxMgmt.templateForm.saveTemplateError": "テンプレートを作成できません",
- "xpack.idxMgmt.templateForm.stepAliases.aliasesDescription": "エイリアスをセットアップして、インデックスに関連付けてください。",
- "xpack.idxMgmt.templateForm.stepAliases.aliasesEditorHelpText": "JSON フォーマットを使用: {code}",
- "xpack.idxMgmt.templateForm.stepAliases.docsButtonLabel": "インデックステンプレートドキュメント",
- "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel": "エイリアスコードエディター",
- "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesLabel": "エイリアス",
- "xpack.idxMgmt.templateForm.stepAliases.stepTitle": "エイリアス (任意)",
"xpack.idxMgmt.templateForm.stepLogistics.docsButtonLabel": "インデックステンプレートドキュメント",
"xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "スペースと {invalidCharactersList} は使用できません。",
"xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsLabel": "インデックスパターン",
@@ -7110,9 +7101,6 @@
"xpack.idxMgmt.templateForm.stepLogistics.stepTitle": "ロジスティクス",
"xpack.idxMgmt.templateForm.stepLogistics.versionDescription": "テンプレートを外部管理システムで識別するための番号です。",
"xpack.idxMgmt.templateForm.stepLogistics.versionTitle": "バージョン",
- "xpack.idxMgmt.templateForm.stepMappings.docsButtonLabel": "マッピングドキュメント",
- "xpack.idxMgmt.templateForm.stepMappings.mappingsDescription": "ドキュメントの保存とインデックス方法を定義します。",
- "xpack.idxMgmt.templateForm.stepMappings.stepTitle": "マッピング (任意)",
"xpack.idxMgmt.templateForm.stepReview.requestTab.descriptionText": "このリクエストは次のインデックステンプレートを作成します。",
"xpack.idxMgmt.templateForm.stepReview.requestTabTitle": "リクエスト",
"xpack.idxMgmt.templateForm.stepReview.stepTitle": "「{templateName}」の詳細の確認",
@@ -7134,12 +7122,6 @@
"xpack.idxMgmt.templateForm.steps.mappingsStepName": "マッピング",
"xpack.idxMgmt.templateForm.steps.settingsStepName": "インデックス設定",
"xpack.idxMgmt.templateForm.steps.summaryStepName": "テンプレートのレビュー",
- "xpack.idxMgmt.templateForm.stepSettings.docsButtonLabel": "インデックス設定ドキュメント",
- "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel": "インデックス設定エディター",
- "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsLabel": "インデックス設定",
- "xpack.idxMgmt.templateForm.stepSettings.settingsDescription": "インデックスの動作を定義します。",
- "xpack.idxMgmt.templateForm.stepSettings.settingsEditorHelpText": "JSON フォーマットを使用: {code}",
- "xpack.idxMgmt.templateForm.stepSettings.stepTitle": "インデックス設定 (任意)",
"xpack.idxMgmt.templateList.table.ilmPolicyColumnDescription": "インデックスライフサイクルポリシー「{policyName}」",
"xpack.idxMgmt.templateList.table.ilmPolicyColumnTitle": "ILM ポリシー",
"xpack.idxMgmt.templateList.table.indexPatternsColumnTitle": "インデックスパターン",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 196034367bcf..fcd2e0bfa5c8 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7076,9 +7076,6 @@
"xpack.idxMgmt.summary.summaryTitle": "常规",
"xpack.idxMgmt.templateCreate.loadingTemplateToCloneDescription": "正在加载要克隆的模板……",
"xpack.idxMgmt.templateCreate.loadingTemplateToCloneErrorMessage": "加载要克隆的模板时出错",
- "xpack.idxMgmt.templateDetails.aliasesTab.noAliasesTitle": "未定义任何别名。",
- "xpack.idxMgmt.templateDetails.mappingsTab.noMappingsTitle": "未定义任何映射。",
- "xpack.idxMgmt.templateDetails.settingsTab.noSettingsTitle": "未定义任何设置。",
"xpack.idxMgmt.templateDetails.summaryTab.ilmPolicyDescriptionListTitle": "ILM 策略",
"xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "索引{numIndexPatterns, plural, one {模式} other {模式}}",
"xpack.idxMgmt.templateDetails.summaryTab.noneDescriptionText": "无",
@@ -7093,12 +7090,6 @@
"xpack.idxMgmt.templateForm.createButtonLabel": "创建模板",
"xpack.idxMgmt.templateForm.saveButtonLabel": "保存模板",
"xpack.idxMgmt.templateForm.saveTemplateError": "无法创建模板",
- "xpack.idxMgmt.templateForm.stepAliases.aliasesDescription": "设置要与索引关联的别名。",
- "xpack.idxMgmt.templateForm.stepAliases.aliasesEditorHelpText": "使用 JSON 格式:{code}",
- "xpack.idxMgmt.templateForm.stepAliases.docsButtonLabel": "索引模板文档",
- "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel": "别名代码编辑器",
- "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesLabel": "别名",
- "xpack.idxMgmt.templateForm.stepAliases.stepTitle": "别名(可选)",
"xpack.idxMgmt.templateForm.stepLogistics.docsButtonLabel": "索引模板文档",
"xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "不允许使用空格和字符 {invalidCharactersList}。",
"xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsLabel": "索引模式",
@@ -7114,9 +7105,6 @@
"xpack.idxMgmt.templateForm.stepLogistics.stepTitle": "运筹",
"xpack.idxMgmt.templateForm.stepLogistics.versionDescription": "在外部管理系统中标识该模板的编号。",
"xpack.idxMgmt.templateForm.stepLogistics.versionTitle": "版本",
- "xpack.idxMgmt.templateForm.stepMappings.docsButtonLabel": "映射文档",
- "xpack.idxMgmt.templateForm.stepMappings.mappingsDescription": "定义如何存储和索引文档。",
- "xpack.idxMgmt.templateForm.stepMappings.stepTitle": "映射(可选)",
"xpack.idxMgmt.templateForm.stepReview.requestTab.descriptionText": "此请求将创建以下索引模板。",
"xpack.idxMgmt.templateForm.stepReview.requestTabTitle": "请求",
"xpack.idxMgmt.templateForm.stepReview.stepTitle": "查看 “{templateName}” 的详情",
@@ -7138,12 +7126,6 @@
"xpack.idxMgmt.templateForm.steps.mappingsStepName": "映射",
"xpack.idxMgmt.templateForm.steps.settingsStepName": "索引设置",
"xpack.idxMgmt.templateForm.steps.summaryStepName": "复查模板",
- "xpack.idxMgmt.templateForm.stepSettings.docsButtonLabel": "索引设置文档",
- "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel": "索引设置编辑器",
- "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsLabel": "索引设置",
- "xpack.idxMgmt.templateForm.stepSettings.settingsDescription": "定义索引的行为。",
- "xpack.idxMgmt.templateForm.stepSettings.settingsEditorHelpText": "使用 JSON 格式:{code}",
- "xpack.idxMgmt.templateForm.stepSettings.stepTitle": "索引设置(可选)",
"xpack.idxMgmt.templateList.table.ilmPolicyColumnDescription": "“{policyName}”索引生命周期策略",
"xpack.idxMgmt.templateList.table.ilmPolicyColumnTitle": "ILM 策略",
"xpack.idxMgmt.templateList.table.indexPatternsColumnTitle": "索引模式",
From 188def4f3166506794af2381af77d4d5e77f0f87 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 23 Jun 2020 15:22:10 +0200
Subject: [PATCH 32/33] [7.x] [RUM Dashboard] Initial version resubmit (#69531)
(#69669)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Casper Hübertz
Co-authored-by: Elastic Machine
Co-authored-by: Casper Hübertz
Co-authored-by: Elastic Machine
---
.../elasticsearch_fieldnames.test.ts.snap | 24 +++
.../apm/common/elasticsearch_fieldnames.ts | 6 +
.../plugins/apm/common/projections/errors.ts | 2 +-
.../plugins/apm/common/projections/metrics.ts | 2 +-
.../apm/common/projections/rum_overview.ts | 46 +++++
.../apm/common/projections/services.ts | 2 +-
.../apm/common/projections/transactions.ts | 2 +-
.../plugins/apm/common/projections/typings.ts | 1 +
.../helpers => common/utils}/range_filter.ts | 0
.../apm/e2e/cypress/integration/helpers.ts | 20 +-
.../cypress/integration/rum_dashboard.feature | 7 +
.../apm/e2e/cypress/integration/snapshots.js | 23 ++-
.../support/step_definitions/rum_dashboard.ts | 43 ++++
x-pack/plugins/apm/e2e/ingest-data/replay.js | 6 +-
x-pack/plugins/apm/e2e/run-e2e.sh | 2 +-
x-pack/plugins/apm/e2e/yarn.lock | 8 +-
.../plugins/apm/public/application/index.tsx | 3 +-
.../apm/public/components/app/Home/index.tsx | 24 ++-
.../app/Main/route_config/index.tsx | 9 +
.../app/Main/route_config/route_names.tsx | 1 +
.../app/RumDashboard/ChartWrapper/index.tsx | 63 ++++++
.../app/RumDashboard/ClientMetrics/index.tsx | 82 ++++++++
.../PercentileAnnotations.tsx | 60 ++++++
.../PageLoadDistribution/index.tsx | 144 +++++++++++++
.../app/RumDashboard/PageViewsTrend/index.tsx | 111 ++++++++++
.../app/RumDashboard/RumDashboard.tsx | 65 ++++++
.../components/app/RumDashboard/index.tsx | 40 ++++
.../app/RumDashboard/translations.ts | 73 +++++++
.../app/ServiceDetails/ServiceDetailTabs.tsx | 16 ++
.../components/shared/KueryBar/index.tsx | 4 +-
.../shared/Links/apm/RumOverviewLink.tsx | 31 +++
.../lib/errors/distribution/get_buckets.ts | 2 +-
.../apm/server/lib/errors/get_error_group.ts | 2 +-
.../apm/server/lib/errors/get_error_rate.ts | 2 +-
.../__snapshots__/queries.test.ts.snap | 194 ++++++++++++++++++
.../lib/rum_client/get_client_metrics.ts | 61 ++++++
.../rum_client/get_page_load_distribution.ts | 140 +++++++++++++
.../lib/rum_client/get_page_view_trends.ts | 57 +++++
.../apm/server/lib/rum_client/queries.test.ts | 52 +++++
.../get_service_map_service_node_info.ts | 2 +-
.../lib/service_map/get_trace_sample_ids.ts | 2 +-
.../get_derived_service_annotations.ts | 2 +-
.../lib/services/get_service_agent_name.ts | 2 +-
.../services/get_service_transaction_types.ts | 2 +-
.../apm/server/lib/traces/get_trace_items.ts | 2 +-
.../avg_duration_by_browser/fetcher.ts | 2 +-
.../avg_duration_by_country/index.ts | 2 +-
.../lib/transactions/breakdown/index.ts | 2 +-
.../charts/get_timeseries_data/fetcher.ts | 2 +-
.../distribution/get_buckets/fetcher.ts | 2 +-
.../lib/transactions/get_transaction/index.ts | 2 +-
.../server/lib/ui_filters/get_environments.ts | 2 +-
.../lib/ui_filters/local_ui_filters/config.ts | 35 ++++
.../apm/server/routes/create_apm_api.ts | 14 +-
.../plugins/apm/server/routes/rum_client.ts | 57 +++++
.../plugins/apm/server/routes/ui_filters.ts | 11 +
.../apm/typings/elasticsearch/aggregations.ts | 26 +++
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
59 files changed, 1553 insertions(+), 48 deletions(-)
create mode 100644 x-pack/plugins/apm/common/projections/rum_overview.ts
rename x-pack/plugins/apm/{server/lib/helpers => common/utils}/range_filter.ts (100%)
create mode 100644 x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature
create mode 100644 x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum_dashboard.ts
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/ChartWrapper/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
create mode 100644 x-pack/plugins/apm/public/components/shared/Links/apm/RumOverviewLink.tsx
create mode 100644 x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
create mode 100644 x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
create mode 100644 x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
create mode 100644 x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
create mode 100644 x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
create mode 100644 x-pack/plugins/apm/server/routes/rum_client.ts
diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap
index 54dd4704edfc..f3dc7abcf823 100644
--- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap
+++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap
@@ -4,6 +4,8 @@ exports[`Error AGENT_NAME 1`] = `"java"`;
exports[`Error AGENT_VERSION 1`] = `"agent version"`;
+exports[`Error CLIENT_GEO 1`] = `undefined`;
+
exports[`Error CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`;
exports[`Error CONTAINER_ID 1`] = `undefined`;
@@ -122,18 +124,26 @@ exports[`Error TRANSACTION_SAMPLED 1`] = `undefined`;
exports[`Error TRANSACTION_TYPE 1`] = `"request"`;
+exports[`Error TRANSACTION_URL 1`] = `undefined`;
+
exports[`Error URL_FULL 1`] = `undefined`;
+exports[`Error USER_AGENT_DEVICE 1`] = `undefined`;
+
exports[`Error USER_AGENT_NAME 1`] = `undefined`;
exports[`Error USER_AGENT_ORIGINAL 1`] = `undefined`;
+exports[`Error USER_AGENT_OS 1`] = `undefined`;
+
exports[`Error USER_ID 1`] = `undefined`;
exports[`Span AGENT_NAME 1`] = `"java"`;
exports[`Span AGENT_VERSION 1`] = `"agent version"`;
+exports[`Span CLIENT_GEO 1`] = `undefined`;
+
exports[`Span CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`;
exports[`Span CONTAINER_ID 1`] = `undefined`;
@@ -252,18 +262,26 @@ exports[`Span TRANSACTION_SAMPLED 1`] = `undefined`;
exports[`Span TRANSACTION_TYPE 1`] = `undefined`;
+exports[`Span TRANSACTION_URL 1`] = `undefined`;
+
exports[`Span URL_FULL 1`] = `undefined`;
+exports[`Span USER_AGENT_DEVICE 1`] = `undefined`;
+
exports[`Span USER_AGENT_NAME 1`] = `undefined`;
exports[`Span USER_AGENT_ORIGINAL 1`] = `undefined`;
+exports[`Span USER_AGENT_OS 1`] = `undefined`;
+
exports[`Span USER_ID 1`] = `undefined`;
exports[`Transaction AGENT_NAME 1`] = `"java"`;
exports[`Transaction AGENT_VERSION 1`] = `"agent version"`;
+exports[`Transaction CLIENT_GEO 1`] = `undefined`;
+
exports[`Transaction CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`;
exports[`Transaction CONTAINER_ID 1`] = `"container1234567890abcdef"`;
@@ -382,10 +400,16 @@ exports[`Transaction TRANSACTION_SAMPLED 1`] = `true`;
exports[`Transaction TRANSACTION_TYPE 1`] = `"transaction type"`;
+exports[`Transaction TRANSACTION_URL 1`] = `undefined`;
+
exports[`Transaction URL_FULL 1`] = `"http://www.elastic.co"`;
+exports[`Transaction USER_AGENT_DEVICE 1`] = `undefined`;
+
exports[`Transaction USER_AGENT_NAME 1`] = `"Other"`;
exports[`Transaction USER_AGENT_ORIGINAL 1`] = `"test original"`;
+exports[`Transaction USER_AGENT_OS 1`] = `undefined`;
+
exports[`Transaction USER_ID 1`] = `"1337"`;
diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts
index d5c3f91eb924..7537dba7f841 100644
--- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts
+++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts
@@ -87,3 +87,9 @@ export const CONTAINER_ID = 'container.id';
export const POD_NAME = 'kubernetes.pod.name';
export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code';
+
+// RUM Labels
+export const TRANSACTION_URL = 'transaction.page.url';
+export const CLIENT_GEO = 'client.geo';
+export const USER_AGENT_DEVICE = 'user_agent.device.name';
+export const USER_AGENT_OS = 'user_agent.os.name';
diff --git a/x-pack/plugins/apm/common/projections/errors.ts b/x-pack/plugins/apm/common/projections/errors.ts
index bd397afae224..390a8a096810 100644
--- a/x-pack/plugins/apm/common/projections/errors.ts
+++ b/x-pack/plugins/apm/common/projections/errors.ts
@@ -16,7 +16,7 @@ import {
ERROR_GROUP_ID,
} from '../elasticsearch_fieldnames';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { rangeFilter } from '../../server/lib/helpers/range_filter';
+import { rangeFilter } from '../utils/range_filter';
export function getErrorGroupsProjection({
setup,
diff --git a/x-pack/plugins/apm/common/projections/metrics.ts b/x-pack/plugins/apm/common/projections/metrics.ts
index b05ec5f2ba87..45998bfe82e9 100644
--- a/x-pack/plugins/apm/common/projections/metrics.ts
+++ b/x-pack/plugins/apm/common/projections/metrics.ts
@@ -16,7 +16,7 @@ import {
SERVICE_NODE_NAME,
} from '../elasticsearch_fieldnames';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { rangeFilter } from '../../server/lib/helpers/range_filter';
+import { rangeFilter } from '../utils/range_filter';
import { SERVICE_NODE_NAME_MISSING } from '../service_nodes';
function getServiceNodeNameFilters(serviceNodeName?: string) {
diff --git a/x-pack/plugins/apm/common/projections/rum_overview.ts b/x-pack/plugins/apm/common/projections/rum_overview.ts
new file mode 100644
index 000000000000..b1218546d09f
--- /dev/null
+++ b/x-pack/plugins/apm/common/projections/rum_overview.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../server/lib/helpers/setup_request';
+import { PROCESSOR_EVENT, TRANSACTION_TYPE } from '../elasticsearch_fieldnames';
+import { rangeFilter } from '../utils/range_filter';
+
+export function getRumOverviewProjection({
+ setup,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+}) {
+ const { start, end, uiFiltersES, indices } = setup;
+
+ const bool = {
+ filter: [
+ { range: rangeFilter(start, end) },
+ { term: { [PROCESSOR_EVENT]: 'transaction' } },
+ { term: { [TRANSACTION_TYPE]: 'page-load' } },
+ {
+ // Adding this filter to cater for some inconsistent rum data
+ exists: {
+ field: 'transaction.marks.navigationTiming.fetchStart',
+ },
+ },
+ ...uiFiltersES,
+ ],
+ };
+
+ return {
+ index: indices['apm_oss.transactionIndices'],
+ body: {
+ query: {
+ bool,
+ },
+ },
+ };
+}
diff --git a/x-pack/plugins/apm/common/projections/services.ts b/x-pack/plugins/apm/common/projections/services.ts
index bcfc27d720ba..80a3471e9c30 100644
--- a/x-pack/plugins/apm/common/projections/services.ts
+++ b/x-pack/plugins/apm/common/projections/services.ts
@@ -12,7 +12,7 @@ import {
} from '../../server/lib/helpers/setup_request';
import { SERVICE_NAME, PROCESSOR_EVENT } from '../elasticsearch_fieldnames';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { rangeFilter } from '../../server/lib/helpers/range_filter';
+import { rangeFilter } from '../utils/range_filter';
export function getServicesProjection({
setup,
diff --git a/x-pack/plugins/apm/common/projections/transactions.ts b/x-pack/plugins/apm/common/projections/transactions.ts
index 99d5a04c1e72..b6cd73ca9aaa 100644
--- a/x-pack/plugins/apm/common/projections/transactions.ts
+++ b/x-pack/plugins/apm/common/projections/transactions.ts
@@ -17,7 +17,7 @@ import {
TRANSACTION_NAME,
} from '../elasticsearch_fieldnames';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { rangeFilter } from '../../server/lib/helpers/range_filter';
+import { rangeFilter } from '../utils/range_filter';
export function getTransactionsProjection({
setup,
diff --git a/x-pack/plugins/apm/common/projections/typings.ts b/x-pack/plugins/apm/common/projections/typings.ts
index 3361770336dd..693795b09e1d 100644
--- a/x-pack/plugins/apm/common/projections/typings.ts
+++ b/x-pack/plugins/apm/common/projections/typings.ts
@@ -29,4 +29,5 @@ export enum PROJECTION {
METRICS = 'metrics',
ERROR_GROUPS = 'errorGroups',
SERVICE_NODES = 'serviceNodes',
+ RUM_OVERVIEW = 'rumOverview',
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/range_filter.ts b/x-pack/plugins/apm/common/utils/range_filter.ts
similarity index 100%
rename from x-pack/plugins/apm/server/lib/helpers/range_filter.ts
rename to x-pack/plugins/apm/common/utils/range_filter.ts
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts b/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts
index 90d5c9eda632..689b88390810 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts
+++ b/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts
@@ -6,20 +6,30 @@
/* eslint-disable import/no-extraneous-dependencies */
-const RANGE_FROM = '2020-03-04T12:30:00.000Z';
-const RANGE_TO = '2020-03-04T13:00:00.000Z';
+const RANGE_FROM = '2020-06-01T14:59:32.686Z';
+const RANGE_TO = '2020-06-16T16:59:36.219Z';
+
const BASE_URL = Cypress.config().baseUrl;
/** The default time in ms to wait for a Cypress command to complete */
export const DEFAULT_TIMEOUT = 60 * 1000;
-export function loginAndWaitForPage(url: string) {
+export function loginAndWaitForPage(
+ url: string,
+ dateRange?: { to: string; from: string }
+) {
const username = Cypress.env('elasticsearch_username');
const password = Cypress.env('elasticsearch_password');
cy.log(`Authenticating via ${username} / ${password}`);
-
- const fullUrl = `${BASE_URL}${url}?rangeFrom=${RANGE_FROM}&rangeTo=${RANGE_TO}`;
+ let rangeFrom = RANGE_FROM;
+ let rangeTo = RANGE_TO;
+ if (dateRange) {
+ rangeFrom = dateRange.from;
+ rangeTo = dateRange.to;
+ }
+
+ const fullUrl = `${BASE_URL}${url}?rangeFrom=${rangeFrom}&rangeTo=${rangeTo}`;
cy.visit(fullUrl, { auth: { username, password } });
cy.viewport('macbook-15');
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature b/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature
new file mode 100644
index 000000000000..eabfaf096731
--- /dev/null
+++ b/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature
@@ -0,0 +1,7 @@
+Feature: RUM Dashboard
+
+ Scenario: Client metrics
+ Given a user browses the APM UI application for RUM Data
+ When the user inspects the real user monitoring tab
+ Then should redirect to rum dashboard
+ And should have correct client metrics
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
index d4c8ba491085..dd96a57ef8c4 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
+++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
@@ -1,10 +1,17 @@
module.exports = {
- APM: {
- 'Transaction duration charts': {
- '1': '350 ms',
- '2': '175 ms',
- '3': '0 ms',
- },
+ "__version": "4.5.0",
+ "APM": {
+ "Transaction duration charts": {
+ "1": "55 ms",
+ "2": "28 ms",
+ "3": "0 ms"
+ }
},
- __version: '4.5.0',
-};
+ "RUM Dashboard": {
+ "Client metrics": {
+ "1": "62",
+ "2": "0.07 sec",
+ "3": "0.01 sec"
+ }
+ }
+}
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum_dashboard.ts
new file mode 100644
index 000000000000..38eadbf51303
--- /dev/null
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum_dashboard.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
+import { loginAndWaitForPage } from '../../integration/helpers';
+
+/** The default time in ms to wait for a Cypress command to complete */
+export const DEFAULT_TIMEOUT = 60 * 1000;
+
+Given(`a user browses the APM UI application for RUM Data`, () => {
+ // open service overview page
+ const RANGE_FROM = 'now-24h';
+ const RANGE_TO = 'now';
+ loginAndWaitForPage(`/app/apm#/services`, { from: RANGE_FROM, to: RANGE_TO });
+});
+
+When(`the user inspects the real user monitoring tab`, () => {
+ // click rum tab
+ cy.get(':contains(Real User Monitoring)', { timeout: DEFAULT_TIMEOUT })
+ .last()
+ .click({ force: true });
+});
+
+Then(`should redirect to rum dashboard`, () => {
+ cy.url().should('contain', `/app/apm#/rum-overview`);
+});
+
+Then(`should have correct client metrics`, () => {
+ const clientMetrics = '[data-cy=client-metrics] .euiStat__title';
+
+ // wait for all loading to finish
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+ cy.get('.euiStat__title-isLoading').should('not.be.visible');
+
+ cy.get(clientMetrics).eq(2).invoke('text').snapshot();
+
+ cy.get(clientMetrics).eq(1).invoke('text').snapshot();
+
+ cy.get(clientMetrics).eq(0).invoke('text').snapshot();
+});
diff --git a/x-pack/plugins/apm/e2e/ingest-data/replay.js b/x-pack/plugins/apm/e2e/ingest-data/replay.js
index ae3f62894afc..3478039f39b5 100644
--- a/x-pack/plugins/apm/e2e/ingest-data/replay.js
+++ b/x-pack/plugins/apm/e2e/ingest-data/replay.js
@@ -99,7 +99,11 @@ async function init() {
.split('\n')
.filter((item) => item)
.map((item) => JSON.parse(item))
- .filter((item) => item.url === '/intake/v2/events');
+ .filter((item) => {
+ return (
+ item.url === '/intake/v2/events' || item.url === '/intake/v2/rum/events'
+ );
+ });
spinner.start();
requestProgress.total = items.length;
diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh
index aa7c0e21425a..43cc74a197f4 100755
--- a/x-pack/plugins/apm/e2e/run-e2e.sh
+++ b/x-pack/plugins/apm/e2e/run-e2e.sh
@@ -109,7 +109,7 @@ echo "${bold}Static mock data (logs: ${E2E_DIR}${TMP_DIR}/ingest-data.log)${norm
# Download static data if not already done
if [ ! -e "${TMP_DIR}/events.json" ]; then
echo 'Downloading events.json...'
- curl --silent https://storage.googleapis.com/apm-ui-e2e-static-data/events.json --output ${TMP_DIR}/events.json
+ curl --silent https://storage.googleapis.com/apm-ui-e2e-static-data/2020-06-12.json --output ${TMP_DIR}/events.json
fi
# echo "Deleting existing indices (apm* and .apm*)"
diff --git a/x-pack/plugins/apm/e2e/yarn.lock b/x-pack/plugins/apm/e2e/yarn.lock
index a6729c56ecb0..975154d71b85 100644
--- a/x-pack/plugins/apm/e2e/yarn.lock
+++ b/x-pack/plugins/apm/e2e/yarn.lock
@@ -5561,10 +5561,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@3.9.2:
- version "3.9.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9"
- integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==
+typescript@3.9.5:
+ version "3.9.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
+ integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
umd@^3.0.0:
version "3.0.3"
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index e7191caced9d..0a6cca8434a8 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -24,7 +24,7 @@ import {
KibanaContextProvider,
useUiSetting$,
} from '../../../../../src/plugins/kibana_react/public';
-import { px, unit, units } from '../style/variables';
+import { px, units } from '../style/variables';
import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { APMIndicesPermission } from '../components/app/APMIndicesPermission';
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
@@ -34,7 +34,6 @@ import { ConfigSchema } from '..';
import 'react-vis/dist/style.css';
const MainContainer = styled.div`
- min-width: ${px(unit * 50)};
padding: ${px(units.plus)};
height: 100%;
`;
diff --git a/x-pack/plugins/apm/public/components/app/Home/index.tsx b/x-pack/plugins/apm/public/components/app/Home/index.tsx
index 74cbc00b1788..c325a7237535 100644
--- a/x-pack/plugins/apm/public/components/app/Home/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Home/index.tsx
@@ -25,6 +25,9 @@ import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'
import { ServiceMap } from '../ServiceMap';
import { ServiceOverview } from '../ServiceOverview';
import { TraceOverview } from '../TraceOverview';
+import { RumOverview } from '../RumDashboard';
+import { RumOverviewLink } from '../../shared/Links/apm/RumOverviewLink';
+import { EndUserExperienceLabel } from '../RumDashboard/translations';
function getHomeTabs({
serviceMapEnabled = true,
@@ -70,14 +73,27 @@ function getHomeTabs({
});
}
+ homeTabs.push({
+ link: (
+
+ {i18n.translate('xpack.apm.home.rumTabLabel', {
+ defaultMessage: 'Real User Monitoring',
+ })}
+
+ ),
+ render: () => ,
+ name: 'rum-overview',
+ });
+
return homeTabs;
}
+
const SETTINGS_LINK_LABEL = i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings',
});
interface Props {
- tab: 'traces' | 'services' | 'service-map';
+ tab: 'traces' | 'services' | 'service-map' | 'rum-overview';
}
export function Home({ tab }: Props) {
@@ -93,7 +109,11 @@ export function Home({ tab }: Props) {
- APM
+
+ {selectedTab.name === 'rum-overview'
+ ? EndUserExperienceLabel
+ : 'APM'}
+
diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx
index 577af75e92d9..295f343b411a 100644
--- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx
@@ -250,4 +250,13 @@ export const routes: BreadcrumbRoute[] = [
}),
name: RouteName.CUSTOMIZE_UI,
},
+ {
+ exact: true,
+ path: '/rum-overview',
+ component: () => ,
+ breadcrumb: i18n.translate('xpack.apm.home.rumOverview.title', {
+ defaultMessage: 'Real User Monitoring',
+ }),
+ name: RouteName.RUM_OVERVIEW,
+ },
];
diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
index 167de1a37f42..4965aa9db876 100644
--- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
+++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
@@ -26,4 +26,5 @@ export enum RouteName {
SERVICE_NODES = 'nodes',
LINK_TO_TRACE = 'link_to_trace',
CUSTOMIZE_UI = 'customize_ui',
+ RUM_OVERVIEW = 'rum_overview',
}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ChartWrapper/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ChartWrapper/index.tsx
new file mode 100644
index 000000000000..a3cfbb28abee
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ChartWrapper/index.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, HTMLAttributes } from 'react';
+import {
+ EuiErrorBoundary,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingChart,
+} from '@elastic/eui';
+
+interface Props {
+ /**
+ * Height for the chart
+ */
+ height?: string;
+ /**
+ * if chart data source is still loading
+ */
+ loading?: boolean;
+ /**
+ * aria-label for accessibility
+ */
+ 'aria-label'?: string;
+}
+
+export const ChartWrapper: FC = ({
+ loading = false,
+ height = '100%',
+ children,
+ ...rest
+}) => {
+ const opacity = loading === true ? 0.3 : 1;
+
+ return (
+
+ )}
+ >
+ {children}
+
+ {loading === true && (
+
+
+
+
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
new file mode 100644
index 000000000000..8c0a7c6a91f6
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+// @flow
+import * as React from 'react';
+import styled from 'styled-components';
+import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';
+import { useFetcher } from '../../../../hooks/useFetcher';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { BackEndLabel, FrontEndLabel, PageViewsLabel } from '../translations';
+
+export const formatBigValue = (val?: number | null, fixed?: number): string => {
+ if (val && val >= 1000) {
+ const result = val / 1000;
+ if (fixed) {
+ return result.toFixed(fixed) + 'k';
+ }
+ return result + 'k';
+ }
+ return val + '';
+};
+
+const ClFlexGroup = styled(EuiFlexGroup)`
+ flex-direction: row;
+ @media only screen and (max-width: 768px) {
+ flex-direction: row;
+ justify-content: space-between;
+ }
+`;
+
+export const ClientMetrics = () => {
+ const { urlParams, uiFilters } = useUrlParams();
+
+ const { start, end } = urlParams;
+
+ const { data, status } = useFetcher(
+ (callApmApi) => {
+ if (start && end) {
+ return callApmApi({
+ pathname: '/api/apm/rum/client-metrics',
+ params: {
+ query: { start, end, uiFilters: JSON.stringify(uiFilters) },
+ },
+ });
+ }
+ },
+ [start, end, uiFilters]
+ );
+
+ const STAT_STYLE = { width: '240px' };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx
new file mode 100644
index 000000000000..9c89b8bc161b
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 * as React from 'react';
+import {
+ AnnotationDomainTypes,
+ LineAnnotation,
+ LineAnnotationDatum,
+ LineAnnotationStyle,
+} from '@elastic/charts';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+import styled from 'styled-components';
+
+interface Props {
+ percentiles?: Record;
+}
+
+function generateAnnotationData(
+ values?: Record
+): LineAnnotationDatum[] {
+ return Object.entries(values ?? {}).map((value, index) => ({
+ dataValue: value[1],
+ details: `${(+value[0]).toFixed(0)}`,
+ }));
+}
+
+const PercentileMarker = styled.span`
+ position: relative;
+ bottom: 140px;
+`;
+
+export const PercentileAnnotations = ({ percentiles }: Props) => {
+ const dataValues = generateAnnotationData(percentiles) ?? [];
+
+ const style: Partial = {
+ line: {
+ strokeWidth: 1,
+ stroke: euiLightVars.euiColorSecondary,
+ opacity: 1,
+ },
+ };
+
+ return (
+ <>
+ {dataValues.map((annotation, index) => (
+ {annotation.details}th}
+ />
+ ))}
+ >
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
new file mode 100644
index 000000000000..c7a0b64f6a8b
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import {
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import {
+ Axis,
+ Chart,
+ ScaleType,
+ LineSeries,
+ CurveType,
+ BrushEndListener,
+ Settings,
+ TooltipValueFormatter,
+ TooltipValue,
+} from '@elastic/charts';
+import { Position } from '@elastic/charts/dist/utils/commons';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { useFetcher } from '../../../../hooks/useFetcher';
+import { ChartWrapper } from '../ChartWrapper';
+import { PercentileAnnotations } from './PercentileAnnotations';
+import {
+ PageLoadDistLabel,
+ PageLoadTimeLabel,
+ PercPageLoadedLabel,
+ ResetZoomLabel,
+} from '../translations';
+
+export const PageLoadDistribution = () => {
+ const { urlParams, uiFilters } = useUrlParams();
+
+ const { start, end } = urlParams;
+
+ const [percentileRange, setPercentileRange] = useState<{
+ min: string | null;
+ max: string | null;
+ }>({
+ min: null,
+ max: null,
+ });
+
+ const { data, status } = useFetcher(
+ (callApmApi) => {
+ if (start && end) {
+ return callApmApi({
+ pathname: '/api/apm/rum-client/page-load-distribution',
+ params: {
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ ...(percentileRange.min && percentileRange.max
+ ? {
+ minPercentile: percentileRange.min,
+ maxPercentile: percentileRange.max,
+ }
+ : {}),
+ },
+ },
+ });
+ }
+ },
+ [end, start, uiFilters, percentileRange.min, percentileRange.max]
+ );
+
+ const onBrushEnd: BrushEndListener = ({ x }) => {
+ if (!x) {
+ return;
+ }
+ const [minX, maxX] = x;
+ setPercentileRange({ min: String(minX), max: String(maxX) });
+ };
+
+ const headerFormatter: TooltipValueFormatter = (tooltip: TooltipValue) => {
+ return (
+
+
{tooltip.value} seconds
+
+ );
+ };
+
+ const tooltipProps = {
+ headerFormatter,
+ };
+
+ return (
+
+
+
+
+ {PageLoadDistLabel}
+
+
+
+ {
+ setPercentileRange({ min: null, max: null });
+ }}
+ fill={percentileRange.min !== null && percentileRange.max !== null}
+ >
+ {ResetZoomLabel}
+
+
+
+
+
+
+
+
+
+ Number(d).toFixed(1) + ' %'}
+ />
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
new file mode 100644
index 000000000000..cc41bd435294
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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 * as React from 'react';
+import { EuiTitle } from '@elastic/eui';
+import {
+ Axis,
+ BarSeries,
+ BrushEndListener,
+ Chart,
+ niceTimeFormatByDay,
+ ScaleType,
+ Settings,
+ timeFormatter,
+} from '@elastic/charts';
+import moment from 'moment';
+import { Position } from '@elastic/charts/dist/utils/commons';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { useFetcher } from '../../../../hooks/useFetcher';
+import { ChartWrapper } from '../ChartWrapper';
+import { DateTimeLabel, PageViewsLabel } from '../translations';
+import { history } from '../../../../utils/history';
+import { fromQuery, toQuery } from '../../../shared/Links/url_helpers';
+import { formatBigValue } from '../ClientMetrics';
+
+export const PageViewsTrend = () => {
+ const { urlParams, uiFilters } = useUrlParams();
+
+ const { start, end } = urlParams;
+
+ const { data, status } = useFetcher(
+ (callApmApi) => {
+ if (start && end) {
+ return callApmApi({
+ pathname: '/api/apm/rum-client/page-view-trends',
+ params: {
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ },
+ },
+ });
+ }
+ },
+ [end, start, uiFilters]
+ );
+ const formatter = timeFormatter(niceTimeFormatByDay(2));
+
+ const onBrushEnd: BrushEndListener = ({ x }) => {
+ if (!x) {
+ return;
+ }
+ const [minX, maxX] = x;
+
+ const rangeFrom = moment(minX).toISOString();
+ const rangeTo = moment(maxX).toISOString();
+
+ history.push({
+ ...history.location,
+ search: fromQuery({
+ ...toQuery(history.location.search),
+ rangeFrom,
+ rangeTo,
+ }),
+ });
+ };
+
+ return (
+
+
+ {PageViewsLabel}
+
+
+
+
+
+ formatBigValue(Number(d))}
+ />
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx
new file mode 100644
index 000000000000..e3fa7374afb3
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTitle,
+ EuiSpacer,
+ EuiPanel,
+} from '@elastic/eui';
+import React from 'react';
+import { ClientMetrics } from './ClientMetrics';
+import { PageViewsTrend } from './PageViewsTrend';
+import { PageLoadDistribution } from './PageLoadDistribution';
+import { getWhatIsGoingOnLabel } from './translations';
+import { useUrlParams } from '../../../hooks/useUrlParams';
+
+export function RumDashboard() {
+ const { urlParams } = useUrlParams();
+
+ const { environment } = urlParams;
+
+ let environmentLabel = environment || 'all environments';
+
+ if (environment === 'ENVIRONMENT_NOT_DEFINED') {
+ environmentLabel = 'undefined environment';
+ }
+
+ return (
+ <>
+
+ {getWhatIsGoingOnLabel(environmentLabel)}
+
+
+
+
+
+
+
+
+ Page load times
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
new file mode 100644
index 000000000000..8f21065b0dab
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
+import React, { useMemo } from 'react';
+import { useTrackPageview } from '../../../../../observability/public';
+import { LocalUIFilters } from '../../shared/LocalUIFilters';
+import { PROJECTION } from '../../../../common/projections/typings';
+import { RumDashboard } from './RumDashboard';
+
+export function RumOverview() {
+ useTrackPageview({ app: 'apm', path: 'rum_overview' });
+ useTrackPageview({ app: 'apm', path: 'rum_overview', delay: 15000 });
+
+ const localUIFiltersConfig = useMemo(() => {
+ const config: React.ComponentProps = {
+ filterNames: ['transactionUrl', 'location', 'device', 'os', 'browser'],
+ projection: PROJECTION.RUM_OVERVIEW,
+ };
+
+ return config;
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
new file mode 100644
index 000000000000..c2aed41a55c7
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const EndUserExperienceLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.title',
+ {
+ defaultMessage: 'End User Experience',
+ }
+);
+
+export const getWhatIsGoingOnLabel = (environmentVal: string) =>
+ i18n.translate('xpack.apm.rum.dashboard.environment.title', {
+ defaultMessage: `What's going on in {environmentVal}?`,
+ values: { environmentVal },
+ });
+
+export const BackEndLabel = i18n.translate('xpack.apm.rum.dashboard.backend', {
+ defaultMessage: 'Backend',
+});
+
+export const FrontEndLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.frontend',
+ {
+ defaultMessage: 'Frontend',
+ }
+);
+
+export const PageViewsLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.pageViews',
+ {
+ defaultMessage: 'Page views',
+ }
+);
+
+export const DateTimeLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.dateTime.label',
+ {
+ defaultMessage: 'Date / Time',
+ }
+);
+
+export const PercPageLoadedLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.pagesLoaded.label',
+ {
+ defaultMessage: 'Pages loaded',
+ }
+);
+
+export const PageLoadTimeLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.pageLoadTime.label',
+ {
+ defaultMessage: 'Page load time (seconds)',
+ }
+);
+
+export const PageLoadDistLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.pageLoadDistribution.label',
+ {
+ defaultMessage: 'Page load distribution',
+ }
+);
+
+export const ResetZoomLabel = i18n.translate(
+ 'xpack.apm.rum.dashboard.resetZoom.label',
+ {
+ defaultMessage: 'Reset zoom',
+ }
+);
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx
index 2f35e329720d..81bdbdad805d 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx
@@ -22,6 +22,8 @@ import { ServiceMap } from '../ServiceMap';
import { ServiceMetrics } from '../ServiceMetrics';
import { ServiceNodeOverview } from '../ServiceNodeOverview';
import { TransactionOverview } from '../TransactionOverview';
+import { RumOverviewLink } from '../../shared/Links/apm/RumOverviewLink';
+import { RumOverview } from '../RumDashboard';
interface Props {
tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map';
@@ -110,6 +112,20 @@ export function ServiceDetailTabs({ tab }: Props) {
tabs.push(serviceMapTab);
}
+ if (isRumAgentName(agentName)) {
+ tabs.push({
+ link: (
+
+ {i18n.translate('xpack.apm.home.rumTabLabel', {
+ defaultMessage: 'Real User Monitoring',
+ })}
+
+ ),
+ render: () => ,
+ name: 'rum-overview',
+ });
+ }
+
const selectedTab = tabs.find((serviceTab) => serviceTab.name === tab);
return (
diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx
index d01deb816085..eab685a4c1ab 100644
--- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx
@@ -76,10 +76,10 @@ export function KueryBar() {
});
// The bar should be disabled when viewing the service map
- const disabled = /\/service-map$/.test(location.pathname);
+ const disabled = /\/(service-map|rum-overview)$/.test(location.pathname);
const disabledPlaceholder = i18n.translate(
'xpack.apm.kueryBar.disabledPlaceholder',
- { defaultMessage: 'Search is not available for service map' }
+ { defaultMessage: 'Search is not available here' }
);
async function onChange(inputValue: string, selectionStart: number) {
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/RumOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/RumOverviewLink.tsx
new file mode 100644
index 000000000000..abca9817bd69
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/Links/apm/RumOverviewLink.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { APMLink, APMLinkExtendProps } from './APMLink';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { pickKeys } from '../../../../../common/utils/pick_keys';
+
+const RumOverviewLink = (props: APMLinkExtendProps) => {
+ const { urlParams } = useUrlParams();
+
+ const persistedFilters = pickKeys(
+ urlParams,
+ 'transactionResult',
+ 'host',
+ 'containerId',
+ 'podName'
+ );
+
+ return ;
+};
+
+export { RumOverviewLink };
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
index 7d96c490fcd7..db36ad1ede91 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
@@ -10,7 +10,7 @@ import {
PROCESSOR_EVENT,
SERVICE_NAME,
} from '../../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import {
Setup,
SetupTimeRange,
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group.ts
index b157abd0b7e7..3d20f84ccfbc 100644
--- a/x-pack/plugins/apm/server/lib/errors/get_error_group.ts
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_group.ts
@@ -12,7 +12,7 @@ import {
} from '../../../common/elasticsearch_fieldnames';
import { PromiseReturnType } from '../../../typings/common';
import { APMError } from '../../../typings/es_schemas/ui/apm_error';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import {
Setup,
SetupTimeRange,
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
index d558e3942a42..e91d3953942d 100644
--- a/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
@@ -10,12 +10,12 @@ import {
} from '../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../common/processor_event';
import { getMetricsDateHistogramParams } from '../helpers/metrics';
-import { rangeFilter } from '../helpers/range_filter';
import {
Setup,
SetupTimeRange,
SetupUIFilters,
} from '../helpers/setup_request';
+import { rangeFilter } from '../../../common/utils/range_filter';
export async function getErrorRate({
serviceName,
diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
new file mode 100644
index 000000000000..7d8f31aaeca7
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
@@ -0,0 +1,194 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`rum client dashboard queries fetches client metrics 1`] = `
+Object {
+ "body": Object {
+ "aggs": Object {
+ "backEnd": Object {
+ "avg": Object {
+ "field": "transaction.marks.agent.timeToFirstByte",
+ "missing": 0,
+ },
+ },
+ "domInteractive": Object {
+ "avg": Object {
+ "field": "transaction.marks.agent.domInteractive",
+ "missing": 0,
+ },
+ },
+ "pageViews": Object {
+ "value_count": Object {
+ "field": "transaction.type",
+ },
+ },
+ },
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "range": Object {
+ "@timestamp": Object {
+ "format": "epoch_millis",
+ "gte": 1528113600000,
+ "lte": 1528977600000,
+ },
+ },
+ },
+ Object {
+ "term": Object {
+ "processor.event": "transaction",
+ },
+ },
+ Object {
+ "term": Object {
+ "transaction.type": "page-load",
+ },
+ },
+ Object {
+ "exists": Object {
+ "field": "transaction.marks.navigationTiming.fetchStart",
+ },
+ },
+ Object {
+ "term": Object {
+ "my.custom.ui.filter": "foo-bar",
+ },
+ },
+ ],
+ },
+ },
+ "size": 0,
+ },
+ "index": "myIndex",
+}
+`;
+
+exports[`rum client dashboard queries fetches page load distribution 1`] = `
+Object {
+ "body": Object {
+ "aggs": Object {
+ "durationMinMax": Object {
+ "min": Object {
+ "field": "transaction.duration.us",
+ "missing": 0,
+ },
+ },
+ "durationPercentiles": Object {
+ "percentiles": Object {
+ "field": "transaction.duration.us",
+ "percents": Array [
+ 50,
+ 75,
+ 90,
+ 95,
+ 99,
+ ],
+ "script": Object {
+ "lang": "painless",
+ "params": Object {
+ "timeUnit": 1000,
+ },
+ "source": "doc['transaction.duration.us'].value / params.timeUnit",
+ },
+ },
+ },
+ },
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "range": Object {
+ "@timestamp": Object {
+ "format": "epoch_millis",
+ "gte": 1528113600000,
+ "lte": 1528977600000,
+ },
+ },
+ },
+ Object {
+ "term": Object {
+ "processor.event": "transaction",
+ },
+ },
+ Object {
+ "term": Object {
+ "transaction.type": "page-load",
+ },
+ },
+ Object {
+ "exists": Object {
+ "field": "transaction.marks.navigationTiming.fetchStart",
+ },
+ },
+ Object {
+ "term": Object {
+ "my.custom.ui.filter": "foo-bar",
+ },
+ },
+ ],
+ },
+ },
+ "size": 0,
+ },
+ "index": "myIndex",
+}
+`;
+
+exports[`rum client dashboard queries fetches page view trends 1`] = `
+Object {
+ "body": Object {
+ "aggs": Object {
+ "pageViews": Object {
+ "aggs": Object {
+ "trans_count": Object {
+ "value_count": Object {
+ "field": "transaction.type",
+ },
+ },
+ },
+ "auto_date_histogram": Object {
+ "buckets": 50,
+ "field": "@timestamp",
+ },
+ },
+ },
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "range": Object {
+ "@timestamp": Object {
+ "format": "epoch_millis",
+ "gte": 1528113600000,
+ "lte": 1528977600000,
+ },
+ },
+ },
+ Object {
+ "term": Object {
+ "processor.event": "transaction",
+ },
+ },
+ Object {
+ "term": Object {
+ "transaction.type": "page-load",
+ },
+ },
+ Object {
+ "exists": Object {
+ "field": "transaction.marks.navigationTiming.fetchStart",
+ },
+ },
+ Object {
+ "term": Object {
+ "my.custom.ui.filter": "foo-bar",
+ },
+ },
+ ],
+ },
+ },
+ "size": 0,
+ },
+ "index": "myIndex",
+}
+`;
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
new file mode 100644
index 000000000000..8b3f733fc402
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { getRumOverviewProjection } from '../../../common/projections/rum_overview';
+import { mergeProjection } from '../../../common/projections/util/merge_projection';
+import {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+} from '../helpers/setup_request';
+
+export async function getClientMetrics({
+ setup,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+}) {
+ const projection = getRumOverviewProjection({
+ setup,
+ });
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ query: {
+ bool: projection.body.query.bool,
+ },
+ aggs: {
+ pageViews: { value_count: { field: 'transaction.type' } },
+ backEnd: {
+ avg: {
+ field: 'transaction.marks.agent.timeToFirstByte',
+ missing: 0,
+ },
+ },
+ domInteractive: {
+ avg: {
+ field: 'transaction.marks.agent.domInteractive',
+ missing: 0,
+ },
+ },
+ },
+ },
+ });
+
+ const { client } = setup;
+
+ const response = await client.search(params);
+ const { backEnd, domInteractive, pageViews } = response.aggregations!;
+
+ // Divide by 1000 to convert ms into seconds
+ return {
+ pageViews,
+ backEnd: { value: (backEnd.value || 0) / 1000 },
+ frontEnd: {
+ value: ((domInteractive.value || 0) - (backEnd.value || 0)) / 1000,
+ },
+ };
+}
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
new file mode 100644
index 000000000000..3c563946e405
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
@@ -0,0 +1,140 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getRumOverviewProjection } from '../../../common/projections/rum_overview';
+import { mergeProjection } from '../../../common/projections/util/merge_projection';
+import {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+} from '../helpers/setup_request';
+
+export async function getPageLoadDistribution({
+ setup,
+ minPercentile,
+ maxPercentile,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+ minPercentile?: string;
+ maxPercentile?: string;
+}) {
+ const projection = getRumOverviewProjection({
+ setup,
+ });
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ query: {
+ bool: projection.body.query.bool,
+ },
+ aggs: {
+ durationMinMax: {
+ min: {
+ field: 'transaction.duration.us',
+ missing: 0,
+ },
+ },
+ durationPercentiles: {
+ percentiles: {
+ field: 'transaction.duration.us',
+ percents: [50, 75, 90, 95, 99],
+ script: {
+ lang: 'painless',
+ source: "doc['transaction.duration.us'].value / params.timeUnit",
+ params: {
+ timeUnit: 1000,
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const { client } = setup;
+
+ const {
+ aggregations,
+ hits: { total },
+ } = await client.search(params);
+
+ if (total.value === 0) {
+ return null;
+ }
+
+ const minDuration = (aggregations?.durationMinMax.value ?? 0) / 1000;
+
+ const minPerc = minPercentile ? +minPercentile : minDuration;
+
+ const maxPercentileQuery =
+ aggregations?.durationPercentiles.values['99.0'] ?? 100;
+
+ const maxPerc = maxPercentile ? +maxPercentile : maxPercentileQuery;
+
+ const pageDist = await getPercentilesDistribution(setup, minPerc, maxPerc);
+ return {
+ pageLoadDistribution: pageDist,
+ percentiles: aggregations?.durationPercentiles.values,
+ };
+}
+
+const getPercentilesDistribution = async (
+ setup: Setup & SetupTimeRange & SetupUIFilters,
+ minPercentiles: number,
+ maxPercentile: number
+) => {
+ const stepValue = (maxPercentile - minPercentiles) / 50;
+ const stepValues = [];
+ for (let i = 1; i < 50; i++) {
+ stepValues.push((stepValue * i + minPercentiles).toFixed(2));
+ }
+
+ const projection = getRumOverviewProjection({
+ setup,
+ });
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ query: {
+ bool: projection.body.query.bool,
+ },
+ aggs: {
+ loadDistribution: {
+ percentile_ranks: {
+ field: 'transaction.duration.us',
+ values: stepValues,
+ keyed: false,
+ script: {
+ lang: 'painless',
+ source: "doc['transaction.duration.us'].value / params.timeUnit",
+ params: {
+ timeUnit: 1000,
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const { client } = setup;
+
+ const { aggregations } = await client.search(params);
+
+ const pageDist = (aggregations?.loadDistribution.values ?? []) as Array<{
+ key: number;
+ value: number;
+ }>;
+
+ return pageDist.map(({ key, value }, index: number, arr) => {
+ return {
+ x: key,
+ y: index === 0 ? value : value - arr[index - 1].value,
+ };
+ });
+};
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
new file mode 100644
index 000000000000..126605206d29
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { getRumOverviewProjection } from '../../../common/projections/rum_overview';
+import { mergeProjection } from '../../../common/projections/util/merge_projection';
+import {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+} from '../helpers/setup_request';
+
+export async function getPageViewTrends({
+ setup,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+}) {
+ const projection = getRumOverviewProjection({
+ setup,
+ });
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ query: {
+ bool: projection.body.query.bool,
+ },
+ aggs: {
+ pageViews: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: 50,
+ },
+ aggs: {
+ trans_count: {
+ value_count: {
+ field: 'transaction.type',
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const { client } = setup;
+
+ const response = await client.search(params);
+
+ const result = response.aggregations?.pageViews.buckets ?? [];
+ return result.map(({ key, trans_count }) => ({
+ x: key,
+ y: trans_count.value,
+ }));
+}
diff --git a/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
new file mode 100644
index 000000000000..5f5a48eced74
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ SearchParamsMock,
+ inspectSearchParams,
+} from '../../../public/utils/testHelpers';
+import { getClientMetrics } from './get_client_metrics';
+import { getPageViewTrends } from './get_page_view_trends';
+import { getPageLoadDistribution } from './get_page_load_distribution';
+
+describe('rum client dashboard queries', () => {
+ let mock: SearchParamsMock;
+
+ afterEach(() => {
+ mock.teardown();
+ });
+
+ it('fetches client metrics', async () => {
+ mock = await inspectSearchParams((setup) =>
+ getClientMetrics({
+ setup,
+ })
+ );
+
+ expect(mock.params).toMatchSnapshot();
+ });
+
+ it('fetches page view trends', async () => {
+ mock = await inspectSearchParams((setup) =>
+ getPageViewTrends({
+ setup,
+ })
+ );
+
+ expect(mock.params).toMatchSnapshot();
+ });
+
+ it('fetches page load distribution', async () => {
+ mock = await inspectSearchParams((setup) =>
+ getPageLoadDistribution({
+ setup,
+ minPercentile: '0',
+ maxPercentile: '99',
+ })
+ );
+ expect(mock.params).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
index d069e9339761..e521efa68738 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
@@ -6,7 +6,7 @@
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { ESFilter } from '../../../typings/elasticsearch';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import {
PROCESSOR_EVENT,
SERVICE_ENVIRONMENT,
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
index 6eba84f2205a..11c3a00f3298 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
@@ -5,7 +5,7 @@
*/
import { uniq, take, sortBy } from 'lodash';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import { ESFilter } from '../../../typings/elasticsearch';
import {
PROCESSOR_EVENT,
diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
index 9829c5cb2518..6da5d195cf19 100644
--- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
+++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
@@ -7,7 +7,7 @@ import { isNumber } from 'lodash';
import { Annotation, AnnotationType } from '../../../../common/annotations';
import { SetupTimeRange, Setup } from '../../helpers/setup_request';
import { ESFilter } from '../../../../typings/elasticsearch';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import {
PROCESSOR_EVENT,
SERVICE_NAME,
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
index 0b016828d5f0..8d75d746c7fc 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
@@ -8,7 +8,7 @@ import {
AGENT_NAME,
SERVICE_NAME,
} from '../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
export async function getServiceAgentName(
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
index 963dea4d8322..d88be4055dc2 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
@@ -8,7 +8,7 @@ import {
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
export async function getServiceTransactionTypes(
diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
index e96b323958fd..f9374558dfee 100644
--- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
+++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
@@ -16,7 +16,7 @@ import {
import { Span } from '../../../typings/es_schemas/ui/span';
import { Transaction } from '../../../typings/es_schemas/ui/transaction';
import { APMError } from '../../../typings/es_schemas/ui/apm_error';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { PromiseValueType } from '../../../typings/common';
diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts
index 90dd41cb9b0c..e3d688b69438 100644
--- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts
@@ -13,7 +13,7 @@ import {
USER_AGENT_NAME,
TRANSACTION_DURATION,
} from '../../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { Options } from '.';
import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
index cc23055e3467..ea6213f64ee3 100644
--- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
@@ -17,7 +17,7 @@ import {
SetupTimeRange,
SetupUIFilters,
} from '../../helpers/setup_request';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
export async function getTransactionAvgDurationByCountry({
diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
index 713423f8953d..5af8b9f78cec 100644
--- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
@@ -20,7 +20,7 @@ import {
SetupTimeRange,
SetupUIFilters,
} from '../../helpers/setup_request';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import { getMetricsDateHistogramParams } from '../../helpers/metrics';
import { MAX_KPIS } from './constants';
import { getVizColorForIndex } from '../../../../common/viz_colors';
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
index 71c40010a2a3..8e19af926ce0 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
@@ -15,7 +15,7 @@ import {
} from '../../../../../common/elasticsearch_fieldnames';
import { PromiseReturnType } from '../../../../../../observability/typings/common';
import { getBucketSize } from '../../../helpers/get_bucket_size';
-import { rangeFilter } from '../../../helpers/range_filter';
+import { rangeFilter } from '../../../../../common/utils/range_filter';
import {
Setup,
SetupTimeRange,
diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts
index 920552d1c1ae..3f8bf635712b 100644
--- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts
@@ -15,7 +15,7 @@ import {
TRANSACTION_SAMPLED,
TRANSACTION_TYPE,
} from '../../../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../../../helpers/range_filter';
+import { rangeFilter } from '../../../../../common/utils/range_filter';
import {
Setup,
SetupTimeRange,
diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
index 60dc16b6a546..a7de93a3bf65 100644
--- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
@@ -10,7 +10,7 @@ import {
TRANSACTION_ID,
} from '../../../../common/elasticsearch_fieldnames';
import { Transaction } from '../../../../typings/es_schemas/ui/transaction';
-import { rangeFilter } from '../../helpers/range_filter';
+import { rangeFilter } from '../../../../common/utils/range_filter';
import {
Setup,
SetupTimeRange,
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
index ccbe7a19d2f8..3fca30634be6 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
@@ -9,7 +9,7 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
} from '../../../common/elasticsearch_fieldnames';
-import { rangeFilter } from '../helpers/range_filter';
+import { rangeFilter } from '../../../common/utils/range_filter';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values';
import { ESFilter } from '../../../typings/elasticsearch';
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
index 8f35664c2599..25a559cb07a3 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
@@ -11,6 +11,11 @@ import {
HOST_NAME,
TRANSACTION_RESULT,
SERVICE_VERSION,
+ TRANSACTION_URL,
+ USER_AGENT_NAME,
+ USER_AGENT_DEVICE,
+ CLIENT_GEO,
+ USER_AGENT_OS,
} from '../../../../common/elasticsearch_fieldnames';
const filtersByName = {
@@ -50,6 +55,36 @@ const filtersByName = {
}),
fieldName: SERVICE_VERSION,
},
+ transactionUrl: {
+ title: i18n.translate('xpack.apm.localFilters.titles.transactionUrl', {
+ defaultMessage: 'Url',
+ }),
+ fieldName: TRANSACTION_URL,
+ },
+ browser: {
+ title: i18n.translate('xpack.apm.localFilters.titles.browser', {
+ defaultMessage: 'Browser',
+ }),
+ fieldName: USER_AGENT_NAME,
+ },
+ device: {
+ title: i18n.translate('xpack.apm.localFilters.titles.device', {
+ defaultMessage: 'Device',
+ }),
+ fieldName: USER_AGENT_DEVICE,
+ },
+ location: {
+ title: i18n.translate('xpack.apm.localFilters.titles.location', {
+ defaultMessage: 'Location',
+ }),
+ fieldName: CLIENT_GEO,
+ },
+ os: {
+ title: i18n.translate('xpack.apm.localFilters.titles.os', {
+ defaultMessage: 'OS',
+ }),
+ fieldName: USER_AGENT_OS,
+ },
};
export type LocalUIFilterName = keyof typeof filtersByName;
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index bdfb49fa3082..a34690aff43b 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -59,6 +59,7 @@ import {
transactionsLocalFiltersRoute,
serviceNodesLocalFiltersRoute,
uiFiltersEnvironmentsRoute,
+ rumOverviewLocalFiltersRoute,
} from './ui_filters';
import { createApi } from './create_api';
import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map';
@@ -70,6 +71,11 @@ import {
listCustomLinksRoute,
customLinkTransactionRoute,
} from './settings/custom_link';
+import {
+ rumClientMetricsRoute,
+ rumPageViewsTrendRoute,
+ rumPageLoadDistributionRoute,
+} from './rum_client';
const createApmApi = () => {
const api = createApi()
@@ -148,7 +154,13 @@ const createApmApi = () => {
.add(updateCustomLinkRoute)
.add(deleteCustomLinkRoute)
.add(listCustomLinksRoute)
- .add(customLinkTransactionRoute);
+ .add(customLinkTransactionRoute)
+
+ // Rum Overview
+ .add(rumOverviewLocalFiltersRoute)
+ .add(rumPageViewsTrendRoute)
+ .add(rumPageLoadDistributionRoute)
+ .add(rumClientMetricsRoute);
return api;
};
diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts
new file mode 100644
index 000000000000..9b5f6529b178
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/rum_client.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 * as t from 'io-ts';
+import { createRoute } from './create_route';
+import { setupRequest } from '../lib/helpers/setup_request';
+import { getClientMetrics } from '../lib/rum_client/get_client_metrics';
+import { rangeRt, uiFiltersRt } from './default_api_types';
+import { getPageViewTrends } from '../lib/rum_client/get_page_view_trends';
+import { getPageLoadDistribution } from '../lib/rum_client/get_page_load_distribution';
+
+export const percentileRangeRt = t.partial({
+ minPercentile: t.string,
+ maxPercentile: t.string,
+});
+
+export const rumClientMetricsRoute = createRoute(() => ({
+ path: '/api/apm/rum/client-metrics',
+ params: {
+ query: t.intersection([uiFiltersRt, rangeRt]),
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+
+ return getClientMetrics({ setup });
+ },
+}));
+
+export const rumPageLoadDistributionRoute = createRoute(() => ({
+ path: '/api/apm/rum-client/page-load-distribution',
+ params: {
+ query: t.intersection([uiFiltersRt, rangeRt, percentileRangeRt]),
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+
+ const {
+ query: { minPercentile, maxPercentile },
+ } = context.params;
+
+ return getPageLoadDistribution({ setup, minPercentile, maxPercentile });
+ },
+}));
+
+export const rumPageViewsTrendRoute = createRoute(() => ({
+ path: '/api/apm/rum-client/page-view-trends',
+ params: {
+ query: t.intersection([uiFiltersRt, rangeRt]),
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ return getPageViewTrends({ setup });
+ },
+}));
diff --git a/x-pack/plugins/apm/server/routes/ui_filters.ts b/x-pack/plugins/apm/server/routes/ui_filters.ts
index 8f4ef94b86ac..280645d4de8d 100644
--- a/x-pack/plugins/apm/server/routes/ui_filters.ts
+++ b/x-pack/plugins/apm/server/routes/ui_filters.ts
@@ -29,6 +29,7 @@ import { createRoute } from './create_route';
import { uiFiltersRt, rangeRt } from './default_api_types';
import { jsonRt } from '../../common/runtime_types/json_rt';
import { getServiceNodesProjection } from '../../common/projections/service_nodes';
+import { getRumOverviewProjection } from '../../common/projections/rum_overview';
export const uiFiltersEnvironmentsRoute = createRoute(() => ({
path: '/api/apm/ui_filters/environments',
@@ -221,6 +222,16 @@ export const serviceNodesLocalFiltersRoute = createLocalFiltersRoute({
}),
});
+export const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({
+ path: '/api/apm/ui_filters/local_filters/rumOverview',
+ getProjection: ({ setup }) => {
+ return getRumOverviewProjection({
+ setup,
+ });
+ },
+ queryRt: t.type({}),
+});
+
type BaseQueryType = typeof localUiBaseQueryRt;
type GetProjection<
diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
index 0739e8e6120b..6ee26caa4ef7 100644
--- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
+++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
@@ -137,6 +137,15 @@ export interface AggregationOptionsByType {
>;
keyed?: boolean;
};
+ auto_date_histogram: {
+ field: string;
+ buckets: number;
+ };
+ percentile_ranks: {
+ field: string;
+ values: string[];
+ keyed?: boolean;
+ };
}
type AggregationType = keyof AggregationOptionsByType;
@@ -301,6 +310,23 @@ interface AggregationResponsePart<
? Record
: { buckets: DateRangeBucket[] };
};
+ auto_date_histogram: {
+ buckets: Array<
+ {
+ doc_count: number;
+ key: number;
+ key_as_string: string;
+ } & BucketSubAggregationResponse<
+ TAggregationOptionsMap['aggs'],
+ TDocument
+ >
+ >;
+ interval: string;
+ };
+
+ percentile_ranks: {
+ values: Record | Array<{ key: number; value: number }>;
+ };
}
// Type for debugging purposes. If you see an error in AggregationResponseMap
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c44d08975182..72ee01cb94c1 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4233,7 +4233,6 @@
"xpack.apm.jvmsTable.noJvmsLabel": "JVM が見つかりませんでした",
"xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非ヒープ領域の平均",
"xpack.apm.jvmsTable.threadCountColumnLabel": "最大スレッド数",
- "xpack.apm.kueryBar.disabledPlaceholder": "サービスマップの検索は利用できません",
"xpack.apm.kueryBar.placeholder": "検索 {event, select,\n transaction {トランザクション}\n metric {メトリック}\n error {エラー}\n other {その他}\n } (E.g. {queryExample})",
"xpack.apm.license.betaBadge": "ベータ",
"xpack.apm.license.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index fcd2e0bfa5c8..a0359cf59e3d 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4236,7 +4236,6 @@
"xpack.apm.jvmsTable.noJvmsLabel": "未找到任何 JVM",
"xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非堆内存平均值",
"xpack.apm.jvmsTable.threadCountColumnLabel": "线程计数最大值",
- "xpack.apm.kueryBar.disabledPlaceholder": "搜索不适用于服务地图",
"xpack.apm.kueryBar.placeholder": "搜索{event, select,\n transaction {事务}\n metric {指标}\n error {错误}\n other {事务、错误和指标}\n }(例如 {queryExample})",
"xpack.apm.license.betaBadge": "公测版",
"xpack.apm.license.betaTooltipMessage": "此功能当前为公测版。如果遇到任何错误或有任何反馈,请报告问题或访问我们的论坛。",
From b81e70af217ac718f7ff73b2d766cc40a128a0b1 Mon Sep 17 00:00:00 2001
From: Alexey Antonov
Date: Tue, 23 Jun 2020 17:31:17 +0300
Subject: [PATCH 33/33] Migrate dashboard mode (#69305) (#69675)
Closes: #67469
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
x-pack/.i18nrc.json | 2 +-
x-pack/index.js | 4 +-
x-pack/legacy/plugins/dashboard_mode/index.js | 64 -------
.../dashboard_mode_request_interceptor.js | 163 ------------------
.../dashboard_mode_request_interceptor.js | 73 --------
x-pack/legacy/plugins/ingest_manager/index.ts | 1 +
.../dashboard_mode/common/constants.ts} | 4 +-
.../dashboard_mode/common/index.ts} | 2 +-
x-pack/plugins/dashboard_mode/kibana.json | 7 +-
x-pack/plugins/dashboard_mode/server/index.ts | 14 +-
...dashboard_mode_request_interceptor.test.ts | 111 ++++++++++++
.../dashboard_mode_request_interceptor.ts | 79 +++++++++
.../server/interceptors/index.ts} | 2 +-
.../plugins/dashboard_mode/server/plugin.ts | 62 +++++++
.../dashboard_mode/server/ui_settings.ts | 34 ++++
.../plugins/ingest_manager/server/plugin.ts | 2 +-
.../test/api_integration/apis/fleet/index.js | 2 +-
x-pack/test/api_integration/apis/index.js | 4 +-
18 files changed, 311 insertions(+), 319 deletions(-)
delete mode 100644 x-pack/legacy/plugins/dashboard_mode/index.js
delete mode 100644 x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js
delete mode 100644 x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js
rename x-pack/{legacy/plugins/dashboard_mode/server/index.js => plugins/dashboard_mode/common/constants.ts} (71%)
rename x-pack/{legacy/plugins/dashboard_mode/common/index.js => plugins/dashboard_mode/common/index.ts} (84%)
create mode 100644 x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts
create mode 100644 x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts
rename x-pack/{legacy/plugins/dashboard_mode/common/constants.js => plugins/dashboard_mode/server/interceptors/index.ts} (72%)
create mode 100644 x-pack/plugins/dashboard_mode/server/plugin.ts
create mode 100644 x-pack/plugins/dashboard_mode/server/ui_settings.ts
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 278968cb4723..36cfdf904d6d 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -11,7 +11,7 @@
"xpack.dashboard": "plugins/dashboard_enhanced",
"xpack.discover": "plugins/discover_enhanced",
"xpack.crossClusterReplication": "plugins/cross_cluster_replication",
- "xpack.dashboardMode": "legacy/plugins/dashboard_mode",
+ "xpack.dashboardMode": "plugins/dashboard_mode",
"xpack.data": "plugins/data_enhanced",
"xpack.embeddableEnhanced": "plugins/embeddable_enhanced",
"xpack.endpoint": "plugins/endpoint",
diff --git a/x-pack/index.js b/x-pack/index.js
index e7dd4886e605..2d2e42650cfa 100644
--- a/x-pack/index.js
+++ b/x-pack/index.js
@@ -7,7 +7,6 @@
import { xpackMain } from './legacy/plugins/xpack_main';
import { monitoring } from './legacy/plugins/monitoring';
import { security } from './legacy/plugins/security';
-import { dashboardMode } from './legacy/plugins/dashboard_mode';
import { beats } from './legacy/plugins/beats_management';
import { spaces } from './legacy/plugins/spaces';
import { ingestManager } from './legacy/plugins/ingest_manager';
@@ -18,8 +17,7 @@ module.exports = function (kibana) {
monitoring(kibana),
spaces(kibana),
security(kibana),
- dashboardMode(kibana),
- beats(kibana),
ingestManager(kibana),
+ beats(kibana),
];
};
diff --git a/x-pack/legacy/plugins/dashboard_mode/index.js b/x-pack/legacy/plugins/dashboard_mode/index.js
deleted file mode 100644
index 1145fad2a8a2..000000000000
--- a/x-pack/legacy/plugins/dashboard_mode/index.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { resolve } from 'path';
-import { i18n } from '@kbn/i18n';
-import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from './common';
-import { createDashboardModeRequestInterceptor } from './server';
-
-// Copied largely from plugins/kibana/index.js. The dashboard viewer includes just the dashboard section of
-// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc)
-// since it's view only, but we want the urls to be the same, so we are using largely the same setup.
-export function dashboardMode(kibana) {
- return new kibana.Plugin({
- id: 'dashboard_mode',
- publicDir: resolve(__dirname, 'public'),
- require: ['kibana', 'elasticsearch', 'xpack_main'],
- uiExports: {
- uiSettingDefaults: {
- [CONFIG_DASHBOARD_ONLY_MODE_ROLES]: {
- name: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle', {
- defaultMessage: 'Dashboards only roles',
- }),
- description: i18n.translate(
- 'xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription',
- {
- defaultMessage: 'Roles that belong to View Dashboards Only mode',
- }
- ),
- value: ['kibana_dashboard_only_user'],
- category: ['dashboard'],
- deprecation: {
- message: i18n.translate(
- 'xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation',
- {
- defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
- }
- ),
- docLinksKey: 'dashboardSettings',
- },
- },
- },
- },
-
- config(Joi) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- }).default();
- },
-
- init(server) {
- server.injectUiAppVars(
- 'dashboardViewer',
- async () => await server.getInjectedUiAppVars('kibana')
- );
-
- if (server.plugins.security) {
- server.ext(createDashboardModeRequestInterceptor());
- }
- },
- });
-}
diff --git a/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js b/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js
deleted file mode 100644
index 3fd9bf5f59d5..000000000000
--- a/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js
+++ /dev/null
@@ -1,163 +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 expect from '@kbn/expect';
-import Hapi from 'hapi';
-
-import { createDashboardModeRequestInterceptor } from '../dashboard_mode_request_interceptor';
-
-const DASHBOARD_ONLY_MODE_ROLE = 'test_dashboard_only_mode_role';
-
-function setup() {
- const server = new Hapi.Server();
-
- server.decorate('request', 'getUiSettingsService', () => {
- return {
- get: () => Promise.resolve([DASHBOARD_ONLY_MODE_ROLE]),
- };
- });
-
- // attach the extension
- server.ext(createDashboardModeRequestInterceptor());
-
- // allow the extension to fake "render an app"
- server.decorate('toolkit', 'renderApp', function (app) {
- // `this` is the `h` response toolkit
- return this.response({ renderApp: true, app });
- });
-
- server.decorate('server', 'newPlatform', {
- setup: {
- core: {
- http: {
- basePath: {
- get: () => '',
- },
- },
- },
- },
- });
-
- server.route({
- path: '/app/{appId}',
- method: 'GET',
- handler(req, h) {
- return h.renderApp({ name: req.params.appId });
- },
- });
-
- // catch all route for determining when we get through the extensions
- server.route({
- path: '/{path*}',
- method: 'GET',
- handler(req) {
- return { catchAll: true, path: `/${req.params.path}` };
- },
- });
-
- return { server };
-}
-
-describe('DashboardOnlyModeRequestInterceptor', () => {
- describe('request is not for dashboad-only user', () => {
- describe('app route', () => {
- it('lets the route render as normal', async () => {
- const { server } = setup();
- const response = await server.inject({
- url: '/app/kibana',
- credentials: {
- roles: ['foo', 'bar'],
- },
- });
-
- expect(response)
- .to.have.property('statusCode', 200)
- .and.have.property('result')
- .eql({
- renderApp: true,
- app: { name: 'kibana' },
- });
- });
- });
-
- describe('non-app route', () => {
- it('lets the route render as normal', async () => {
- const { server } = setup();
- const response = await server.inject({
- url: '/foo/bar',
- credentials: {
- roles: ['foo', 'bar'],
- },
- });
-
- expect(response).to.have.property('statusCode', 200).and.have.property('result').eql({
- catchAll: true,
- path: '/foo/bar',
- });
- });
- });
- });
-
- describe('request for dashboard-only user', () => {
- describe('non-kibana app route', () => {
- it('responds with 404', async () => {
- const { server } = setup();
- const response = await server.inject({
- url: '/app/foo',
- credentials: {
- roles: [DASHBOARD_ONLY_MODE_ROLE],
- },
- });
-
- expect(response).to.have.property('statusCode', 404);
- });
- });
-
- describe('requests to dashboard_mode app', () => {
- it('lets the route render as normal', async () => {
- const { server } = setup();
- const response = await server.inject({
- url: '/app/dashboard_mode',
- credentials: {
- roles: [DASHBOARD_ONLY_MODE_ROLE],
- },
- });
-
- expect(response)
- .to.have.property('statusCode', 200)
- .and.have.property('result')
- .eql({
- renderApp: true,
- app: { name: 'dashboard_mode' },
- });
- });
- });
-
- function testRedirectToDashboardModeApp(url) {
- describe(`requests to url:"${url}"`, () => {
- it('redirects to the dashboard_mode app instead', async () => {
- const { server } = setup();
- const response = await server.inject({
- url: url,
- credentials: {
- roles: [DASHBOARD_ONLY_MODE_ROLE],
- },
- });
-
- expect(response).to.have.property('statusCode', 301);
- expect(response.headers).to.have.property('location', '/app/dashboard_mode');
- });
- });
- }
-
- testRedirectToDashboardModeApp('/app/kibana');
- testRedirectToDashboardModeApp('/app/kibana#/foo/bar');
- testRedirectToDashboardModeApp('/app/kibana/foo/bar');
- testRedirectToDashboardModeApp('/app/kibana?foo=bar');
- testRedirectToDashboardModeApp('/app/dashboards?foo=bar');
- testRedirectToDashboardModeApp('/app/home?foo=bar');
- });
-});
diff --git a/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js b/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js
deleted file mode 100644
index 76a582d6cf23..000000000000
--- a/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js
+++ /dev/null
@@ -1,73 +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 Boom from 'boom';
-
-import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from '../common';
-
-const superuserRole = 'superuser';
-
-/**
- * Intercept all requests after auth has completed and apply filtering
- * logic to enforce dashboard only mode.
- *
- * @type {Hapi.RequestExtension}
- */
-export function createDashboardModeRequestInterceptor() {
- return {
- type: 'onPostAuth',
- async method(request, h) {
- const { auth, url } = request;
- const user = auth.credentials;
- const roles = user ? user.roles : [];
-
- if (!user) {
- return h.continue;
- }
-
- const isAppRequest = url.path.startsWith('/app/');
-
- // The act of retrieving this setting ends up creating the config document if it doesn't already exist.
- // Various functional tests have come to indirectly rely on this behavior, so changing this is non-trivial.
- // This will be addressed once dashboard-only-mode is removed altogether.
- const uiSettings = request.getUiSettingsService();
- const dashboardOnlyModeRoles = await uiSettings.get(CONFIG_DASHBOARD_ONLY_MODE_ROLES);
-
- if (!isAppRequest || !dashboardOnlyModeRoles || !roles || roles.length === 0) {
- return h.continue;
- }
-
- const isDashboardOnlyModeUser = user.roles.find((role) =>
- dashboardOnlyModeRoles.includes(role)
- );
- const isSuperUser = user.roles.find((role) => role === superuserRole);
-
- const enforceDashboardOnlyMode = isDashboardOnlyModeUser && !isSuperUser;
- if (enforceDashboardOnlyMode) {
- if (
- url.path.startsWith('/app/home') ||
- url.path.startsWith('/app/kibana') ||
- url.path.startsWith('/app/dashboards')
- ) {
- const basePath = request.server.newPlatform.setup.core.http.basePath.get(request);
- const url = `${basePath}/app/dashboard_mode`;
- // If the user is in "Dashboard only mode" they should only be allowed to see
- // the dashboard app and none others. If the kibana app is requested, this might be a old
- // url we will migrate on the fly.
- return h.redirect(url).permanent().takeover();
- }
- if (url.path.startsWith('/app/dashboard_mode')) {
- // let through requests to the dashboard_mode app
- return h.continue;
- }
-
- throw Boom.notFound();
- }
-
- return h.continue;
- },
- };
-}
diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts
index df9923d9f11e..2b20bf16f240 100644
--- a/x-pack/legacy/plugins/ingest_manager/index.ts
+++ b/x-pack/legacy/plugins/ingest_manager/index.ts
@@ -8,6 +8,7 @@ import { resolve } from 'path';
export function ingestManager(kibana: any) {
return new kibana.Plugin({
id: 'ingestManager',
+ require: ['kibana', 'elasticsearch', 'xpack_main'],
publicDir: resolve(__dirname, '../../../plugins/ingest_manager/public'),
});
}
diff --git a/x-pack/legacy/plugins/dashboard_mode/server/index.js b/x-pack/plugins/dashboard_mode/common/constants.ts
similarity index 71%
rename from x-pack/legacy/plugins/dashboard_mode/server/index.js
rename to x-pack/plugins/dashboard_mode/common/constants.ts
index a7193bb560ca..f5d36fe9799c 100644
--- a/x-pack/legacy/plugins/dashboard_mode/server/index.js
+++ b/x-pack/plugins/dashboard_mode/common/constants.ts
@@ -4,4 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { createDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor';
+export const UI_SETTINGS = {
+ CONFIG_DASHBOARD_ONLY_MODE_ROLES: 'xpackDashboardMode:roles',
+};
diff --git a/x-pack/legacy/plugins/dashboard_mode/common/index.js b/x-pack/plugins/dashboard_mode/common/index.ts
similarity index 84%
rename from x-pack/legacy/plugins/dashboard_mode/common/index.js
rename to x-pack/plugins/dashboard_mode/common/index.ts
index 358d0d5b7e07..60cf0060636d 100644
--- a/x-pack/legacy/plugins/dashboard_mode/common/index.js
+++ b/x-pack/plugins/dashboard_mode/common/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export * from './constants';
+export { UI_SETTINGS } from './constants';
diff --git a/x-pack/plugins/dashboard_mode/kibana.json b/x-pack/plugins/dashboard_mode/kibana.json
index dfe322102509..4777b9b25be2 100644
--- a/x-pack/plugins/dashboard_mode/kibana.json
+++ b/x-pack/plugins/dashboard_mode/kibana.json
@@ -3,10 +3,13 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": [
- "xpack", "dashboard_mode"
+ "xpack",
+ "dashboard_mode"
],
+ "optionalPlugins": ["security"],
"requiredPlugins": [
- "kibanaLegacy", "dashboard"
+ "kibanaLegacy",
+ "dashboard"
],
"server": true,
"ui": true
diff --git a/x-pack/plugins/dashboard_mode/server/index.ts b/x-pack/plugins/dashboard_mode/server/index.ts
index 2a8890c2f81a..671a398734ac 100644
--- a/x-pack/plugins/dashboard_mode/server/index.ts
+++ b/x-pack/plugins/dashboard_mode/server/index.ts
@@ -4,17 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PluginConfigDescriptor } from 'kibana/server';
-
+import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server';
import { schema } from '@kbn/config-schema';
+import { DashboardModeServerPlugin } from './plugin';
+
export const config: PluginConfigDescriptor = {
schema: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
};
-export const plugin = () => ({
- setup() {},
- start() {},
-});
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new DashboardModeServerPlugin(initializerContext);
+}
+
+export { DashboardModeServerPlugin as Plugin };
diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts
new file mode 100644
index 000000000000..2978c48af741
--- /dev/null
+++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts
@@ -0,0 +1,111 @@
+/*
+ * 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 {
+ OnPostAuthHandler,
+ OnPostAuthToolkit,
+ KibanaRequest,
+ LifecycleResponseFactory,
+ IUiSettingsClient,
+} from 'kibana/server';
+import { coreMock } from '../../../../../src/core/server/mocks';
+
+import { AuthenticatedUser } from '../../../security/server';
+import { securityMock } from '../../../security/server/mocks';
+
+import { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor';
+
+const DASHBOARD_ONLY_MODE_ROLE = 'test_dashboard_only_mode_role';
+
+describe('DashboardOnlyModeRequestInterceptor', () => {
+ const core = coreMock.createSetup();
+ const security = securityMock.createSetup();
+
+ let interceptor: OnPostAuthHandler;
+ let toolkit: OnPostAuthToolkit;
+ let uiSettingsMock: any;
+
+ beforeEach(() => {
+ toolkit = {
+ next: jest.fn(),
+ };
+ interceptor = setupDashboardModeRequestInterceptor({
+ http: core.http,
+ security,
+ getUiSettingsClient: () =>
+ (Promise.resolve({
+ get: () => Promise.resolve(uiSettingsMock),
+ }) as unknown) as Promise,
+ });
+ });
+
+ test('should not redirects for not app/* requests', async () => {
+ const request = ({
+ url: {
+ path: 'api/test',
+ },
+ } as unknown) as KibanaRequest;
+
+ interceptor(request, {} as LifecycleResponseFactory, toolkit);
+
+ expect(toolkit.next).toHaveBeenCalled();
+ });
+
+ test('should not redirects not authenticated users', async () => {
+ const request = ({
+ url: {
+ path: '/app/home',
+ },
+ } as unknown) as KibanaRequest;
+
+ interceptor(request, {} as LifecycleResponseFactory, toolkit);
+
+ expect(toolkit.next).toHaveBeenCalled();
+ });
+
+ describe('request for dashboard-only user', () => {
+ function testRedirectToDashboardModeApp(url: string) {
+ describe(`requests to url:"${url}"`, () => {
+ test('redirects to the dashboard_mode app instead', async () => {
+ const request = ({
+ url: {
+ path: url,
+ },
+ credentials: {
+ roles: [DASHBOARD_ONLY_MODE_ROLE],
+ },
+ } as unknown) as KibanaRequest;
+
+ const response = ({
+ redirected: jest.fn(),
+ } as unknown) as LifecycleResponseFactory;
+
+ security.authc.getCurrentUser = jest.fn(
+ (r: KibanaRequest) =>
+ ({
+ roles: [DASHBOARD_ONLY_MODE_ROLE],
+ } as AuthenticatedUser)
+ );
+
+ uiSettingsMock = [DASHBOARD_ONLY_MODE_ROLE];
+
+ await interceptor(request, response, toolkit);
+
+ expect(response.redirected).toHaveBeenCalledWith({
+ headers: { location: `/mock-server-basepath/app/dashboard_mode` },
+ });
+ });
+ });
+ }
+
+ testRedirectToDashboardModeApp('/app/kibana');
+ testRedirectToDashboardModeApp('/app/kibana#/foo/bar');
+ testRedirectToDashboardModeApp('/app/kibana/foo/bar');
+ testRedirectToDashboardModeApp('/app/kibana?foo=bar');
+ testRedirectToDashboardModeApp('/app/dashboards?foo=bar');
+ testRedirectToDashboardModeApp('/app/home?foo=bar');
+ });
+});
diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts
new file mode 100644
index 000000000000..4378c818f087
--- /dev/null
+++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HttpServiceSetup, OnPostAuthHandler, IUiSettingsClient } from 'kibana/server';
+import { SecurityPluginSetup } from '../../../security/server';
+import { UI_SETTINGS } from '../../common';
+
+const superuserRole = 'superuser';
+
+interface DashboardModeRequestInterceptorDependencies {
+ http: HttpServiceSetup;
+ security: SecurityPluginSetup;
+ getUiSettingsClient: () => Promise;
+}
+
+export const setupDashboardModeRequestInterceptor = ({
+ http,
+ security,
+ getUiSettingsClient,
+}: DashboardModeRequestInterceptorDependencies) =>
+ (async (request, response, toolkit) => {
+ const path = request.url.path || '';
+ const isAppRequest = path.startsWith('/app/');
+
+ if (!isAppRequest) {
+ return toolkit.next();
+ }
+
+ const authenticatedUser = security.authc.getCurrentUser(request);
+ const roles = authenticatedUser?.roles || [];
+
+ if (!authenticatedUser || roles.length === 0) {
+ return toolkit.next();
+ }
+
+ const uiSettings = await getUiSettingsClient();
+ const dashboardOnlyModeRoles = await uiSettings.get(
+ UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES
+ );
+
+ if (!dashboardOnlyModeRoles) {
+ return toolkit.next();
+ }
+
+ const isDashboardOnlyModeUser = roles.find((role) => dashboardOnlyModeRoles.includes(role));
+ const isSuperUser = roles.find((role) => role === superuserRole);
+
+ const enforceDashboardOnlyMode = isDashboardOnlyModeUser && !isSuperUser;
+
+ if (enforceDashboardOnlyMode) {
+ if (
+ path.startsWith('/app/home') ||
+ path.startsWith('/app/kibana') ||
+ path.startsWith('/app/dashboards')
+ ) {
+ const dashBoardModeUrl = `${http.basePath.get(request)}/app/dashboard_mode`;
+ // If the user is in "Dashboard only mode" they should only be allowed to see
+ // the dashboard app and none others.
+
+ return response.redirected({
+ headers: {
+ location: dashBoardModeUrl,
+ },
+ });
+ }
+
+ if (path.startsWith('/app/dashboard_mode')) {
+ // let through requests to the dashboard_mode app
+ return toolkit.next();
+ }
+
+ return response.notFound();
+ }
+
+ return toolkit.next();
+ }) as OnPostAuthHandler;
diff --git a/x-pack/legacy/plugins/dashboard_mode/common/constants.js b/x-pack/plugins/dashboard_mode/server/interceptors/index.ts
similarity index 72%
rename from x-pack/legacy/plugins/dashboard_mode/common/constants.js
rename to x-pack/plugins/dashboard_mode/server/interceptors/index.ts
index c9a2378ac5d8..e0bf175d1502 100644
--- a/x-pack/legacy/plugins/dashboard_mode/common/constants.js
+++ b/x-pack/plugins/dashboard_mode/server/interceptors/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const CONFIG_DASHBOARD_ONLY_MODE_ROLES = 'xpackDashboardMode:roles';
+export { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor';
diff --git a/x-pack/plugins/dashboard_mode/server/plugin.ts b/x-pack/plugins/dashboard_mode/server/plugin.ts
new file mode 100644
index 000000000000..8b56f71b667c
--- /dev/null
+++ b/x-pack/plugins/dashboard_mode/server/plugin.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 {
+ PluginInitializerContext,
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ SavedObjectsClient,
+ Logger,
+} from '../../../../src/core/server';
+
+import { SecurityPluginSetup } from '../../security/server';
+import { setupDashboardModeRequestInterceptor } from './interceptors';
+
+import { getUiSettings } from './ui_settings';
+
+interface DashboardModeServerSetupDependencies {
+ security?: SecurityPluginSetup;
+}
+
+export class DashboardModeServerPlugin implements Plugin {
+ private initializerContext: PluginInitializerContext;
+ private logger?: Logger;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.initializerContext = initializerContext;
+ }
+
+ public setup(core: CoreSetup, { security }: DashboardModeServerSetupDependencies) {
+ this.logger = this.initializerContext.logger.get();
+
+ core.uiSettings.register(getUiSettings());
+
+ const getUiSettingsClient = async () => {
+ const [coreStart] = await core.getStartServices();
+ const { savedObjects, uiSettings } = coreStart;
+ const savedObjectsClient = new SavedObjectsClient(savedObjects.createInternalRepository());
+
+ return uiSettings.asScopedToClient(savedObjectsClient);
+ };
+
+ if (security) {
+ const dashboardModeRequestInterceptor = setupDashboardModeRequestInterceptor({
+ http: core.http,
+ security,
+ getUiSettingsClient,
+ });
+
+ core.http.registerOnPostAuth(dashboardModeRequestInterceptor);
+
+ this.logger.debug(`registered DashboardModeRequestInterceptor`);
+ }
+ }
+
+ public start(core: CoreStart) {}
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/dashboard_mode/server/ui_settings.ts b/x-pack/plugins/dashboard_mode/server/ui_settings.ts
new file mode 100644
index 000000000000..f692ec8a33fc
--- /dev/null
+++ b/x-pack/plugins/dashboard_mode/server/ui_settings.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
+import { UiSettingsParams } from 'kibana/server';
+import { UI_SETTINGS } from '../common';
+
+const DASHBOARD_ONLY_USER_ROLE = 'kibana_dashboard_only_user';
+
+export function getUiSettings(): Record> {
+ return {
+ [UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES]: {
+ name: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle', {
+ defaultMessage: 'Dashboards only roles',
+ }),
+ description: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription', {
+ defaultMessage: 'Roles that belong to View Dashboards Only mode',
+ }),
+ value: [DASHBOARD_ONLY_USER_ROLE],
+ category: ['dashboard'],
+ deprecation: {
+ message: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', {
+ defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
+ }),
+ docLinksKey: 'dashboardSettings',
+ },
+ schema: schema.arrayOf(schema.string()),
+ },
+ };
+}
diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts
index ba826e96e40b..ca13304dcc0d 100644
--- a/x-pack/plugins/ingest_manager/server/plugin.ts
+++ b/x-pack/plugins/ingest_manager/server/plugin.ts
@@ -217,7 +217,7 @@ export class IngestManagerPlugin
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
}
) {
- appContextService.start({
+ await appContextService.start({
encryptedSavedObjectsStart: plugins.encryptedSavedObjects,
encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup,
security: this.security,
diff --git a/x-pack/test/api_integration/apis/fleet/index.js b/x-pack/test/api_integration/apis/fleet/index.js
index a8c026ac6a1b..df81b826132a 100644
--- a/x-pack/test/api_integration/apis/fleet/index.js
+++ b/x-pack/test/api_integration/apis/fleet/index.js
@@ -6,6 +6,7 @@
export default function loadTests({ loadTestFile }) {
describe('Fleet Endpoints', () => {
+ loadTestFile(require.resolve('./setup'));
loadTestFile(require.resolve('./delete_agent'));
loadTestFile(require.resolve('./list_agent'));
loadTestFile(require.resolve('./unenroll_agent'));
@@ -16,7 +17,6 @@ export default function loadTests({ loadTestFile }) {
loadTestFile(require.resolve('./enrollment_api_keys/crud'));
loadTestFile(require.resolve('./install'));
loadTestFile(require.resolve('./agents/actions'));
- loadTestFile(require.resolve('./setup'));
loadTestFile(require.resolve('./agent_flow'));
});
}
diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js
index b79dc3f3ffe5..3f3294c85d6d 100644
--- a/x-pack/test/api_integration/apis/index.js
+++ b/x-pack/test/api_integration/apis/index.js
@@ -27,9 +27,9 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./short_urls'));
loadTestFile(require.resolve('./lens'));
loadTestFile(require.resolve('./fleet'));
- loadTestFile(require.resolve('./ingest_manager'));
- loadTestFile(require.resolve('./endpoint'));
loadTestFile(require.resolve('./ml'));
loadTestFile(require.resolve('./transform'));
+ loadTestFile(require.resolve('./endpoint'));
+ loadTestFile(require.resolve('./ingest_manager'));
});
}