From c33138e5cb2bec40830537e4009ddf4a75ab8bb1 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 22 Jun 2021 14:13:48 -0400
Subject: [PATCH 01/78] [Rollups] Migrate to new page layout (#102268)
---
...-plugin-core-public.doclinksstart.links.md | 1 +
...kibana-plugin-core-public.doclinksstart.md | 2 +-
.../public/doc_links/doc_links_service.ts | 2 +
src/core/public/public.api.md | 1 +
.../rollup/public/crud_app/_crud_app.scss | 8 -
.../sections/job_create/job_create.js | 57 ++---
.../job_list/detail_panel/detail_panel.js | 2 +-
.../detail_panel/detail_panel.test.js | 2 +-
.../crud_app/sections/job_list/job_list.js | 214 +++++++++---------
.../sections/job_list/job_list.test.js | 26 ++-
.../sections/job_list/job_table/job_table.js | 23 +-
.../job_list/job_table/job_table.test.js | 8 +
.../crud_app/store/actions/load_jobs.js | 13 +-
.../plugins/rollup/public/shared_imports.ts | 6 +-
.../test/client_integration/job_list.test.js | 5 +-
.../client_integration/job_list_clone.test.js | 9 +
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
18 files changed, 198 insertions(+), 183 deletions(-)
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index d3d76079cdc2a..ae433e3db14c6 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -116,6 +116,7 @@ readonly links: {
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
+ readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
readonly guide: string;
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 34279cef198bf..b0800c7dfc65e 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
}
| |
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 95091a761639b..8c52d09f82159 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -137,6 +137,7 @@ export class DocLinksService {
addData: `${KIBANA_DOCS}connect-to-elasticsearch.html`,
kibana: `${KIBANA_DOCS}index.html`,
upgradeAssistant: `${KIBANA_DOCS}upgrade-assistant.html`,
+ rollupJobs: `${KIBANA_DOCS}data-rollups.html`,
elasticsearch: {
docsBase: `${ELASTICSEARCH_DOCS}`,
asyncSearch: `${ELASTICSEARCH_DOCS}async-search-intro.html`,
@@ -532,6 +533,7 @@ export interface DocLinksStart {
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
+ readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
readonly guide: string;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 6cc2b3f321fb7..27569935bcc65 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -595,6 +595,7 @@ export interface DocLinksStart {
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
+ readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
readonly guide: string;
diff --git a/x-pack/plugins/rollup/public/crud_app/_crud_app.scss b/x-pack/plugins/rollup/public/crud_app/_crud_app.scss
index 9e3bd491115ce..ddf69167145f1 100644
--- a/x-pack/plugins/rollup/public/crud_app/_crud_app.scss
+++ b/x-pack/plugins/rollup/public/crud_app/_crud_app.scss
@@ -4,11 +4,3 @@
.rollupJobWizardStepActions {
align-items: flex-end; /* 1 */
}
-
-/**
- * 1. Ensure panel fills width of parent when search input yields no matching rollup jobs.
- */
-.rollupJobsListPanel {
- // sass-lint:disable-block no-important
- flex-grow: 1 !important; /* 1 */
-}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
index fa3ce260424f2..6f22345dc1cec 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { cloneDeep, debounce, first, mapValues } from 'lodash';
@@ -18,11 +18,10 @@ import {
EuiCallOut,
EuiLoadingKibana,
EuiOverlayMask,
- EuiPageContent,
- EuiPageContentHeader,
+ EuiPageContentBody,
+ EuiPageHeader,
EuiSpacer,
EuiStepsHorizontal,
- EuiTitle,
} from '@elastic/eui';
import {
@@ -522,44 +521,46 @@ export class JobCreateUi extends Component {
}
saveErrorFeedback = (
-
+ <>
+
+
{errorBody}
-
+ >
);
}
return (
-
-
-
-
-
-
-
-
-
-
- {saveErrorFeedback}
-
-
+
+
+ }
+ />
-
+
+
+
+
+ {saveErrorFeedback}
+
+
+
+ {this.renderCurrentStep()}
- {this.renderCurrentStep()}
+
-
+ {this.renderNavigation()}
- {this.renderNavigation()}
-
{savingFeedback}
-
+
);
}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
index 4fe1674e8c643..5e97ff5e2980d 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
@@ -195,7 +195,7 @@ export class DetailPanel extends Component {
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js
index 16919b8388e2e..e1f9ec2b3a315 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js
@@ -70,7 +70,7 @@ describe(' ', () => {
({ component, find, exists } = initTestBed({ isLoading: true }));
const loading = find('rollupJobDetailLoading');
expect(loading.length).toBeTruthy();
- expect(loading.text()).toEqual('Loading rollup job...');
+ expect(loading.text()).toEqual('Loading rollup job…');
// Make sure the title and the tabs are visible
expect(exists('detailPanelTabSelected')).toBeTruthy();
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js
index 589546a11ef38..b2448eb610774 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js
@@ -12,24 +12,19 @@ import { i18n } from '@kbn/i18n';
import {
EuiButton,
+ EuiButtonEmpty,
EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingSpinner,
+ EuiPageHeader,
EuiPageContent,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
EuiSpacer,
- EuiText,
- EuiTextColor,
- EuiTitle,
- EuiCallOut,
} from '@elastic/eui';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
-import { extractQueryParams } from '../../../shared_imports';
+import { extractQueryParams, SectionLoading } from '../../../shared_imports';
import { getRouterLinkProps, listBreadcrumb } from '../../services';
+import { documentationLinks } from '../../services/documentation_links';
+
import { JobTable } from './job_table';
import { DetailPanel } from './detail_panel';
@@ -87,38 +82,26 @@ export class JobListUi extends Component {
this.props.closeDetailPanel();
}
- getHeaderSection() {
- return (
-
-
-
-
-
-
-
- );
- }
-
renderNoPermission() {
const title = i18n.translate('xpack.rollupJobs.jobList.noPermissionTitle', {
defaultMessage: 'Permission error',
});
return (
-
- {this.getHeaderSection()}
-
-
+
-
-
-
+ iconType="alert"
+ title={{title} }
+ body={
+
+
+
+ }
+ />
+
);
}
@@ -130,101 +113,110 @@ export class JobListUi extends Component {
const title = i18n.translate('xpack.rollupJobs.jobList.loadingErrorTitle', {
defaultMessage: 'Error loading rollup jobs',
});
+
return (
-
- {this.getHeaderSection()}
-
-
- {statusCode} {errorString}
-
-
+
+ {title}}
+ body={
+
+ {statusCode} {errorString}
+
+ }
+ />
+
);
}
renderEmpty() {
return (
-
-
-
- }
- body={
-
-
+
+
+
+ }
+ body={
+
+
+
+
+
+ }
+ actions={
+
+
-
-
- }
- actions={
-
-
-
- }
- />
+
+ }
+ />
+
);
}
renderLoading() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
);
}
renderList() {
- const { isLoading } = this.props;
-
return (
-
-
- {this.getHeaderSection()}
-
-
-
+ <>
+
+
+
+ }
+ rightSideItems={[
+
-
-
-
+ ,
+ ]}
+ />
- {isLoading ? this.renderLoading() : }
+
+
+
-
+ >
);
}
@@ -241,15 +233,13 @@ export class JobListUi extends Component {
}
} else if (!isLoading && !hasJobs) {
content = this.renderEmpty();
+ } else if (isLoading) {
+ content = this.renderLoading();
} else {
content = this.renderList();
}
- return (
-
- {content}
-
- );
+ return content;
}
}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js
index 3283f4f521fc0..b2c738a033b3c 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js
@@ -22,6 +22,15 @@ jest.mock('../../services', () => {
};
});
+jest.mock('../../services/documentation_links', () => {
+ const coreMocks = jest.requireActual('../../../../../../../src/core/public/mocks');
+
+ return {
+ init: jest.fn(),
+ documentationLinks: coreMocks.docLinksServiceMock.createStartContract().links,
+ };
+});
+
const defaultProps = {
history: { location: {} },
loadJobs: () => {},
@@ -52,14 +61,14 @@ describe(' ', () => {
it('should display a loading message when loading the jobs', () => {
const { component, exists } = initTestBed({ isLoading: true });
- expect(exists('jobListLoading')).toBeTruthy();
+ expect(exists('sectionLoading')).toBeTruthy();
expect(component.find('JobTable').length).toBeFalsy();
});
it('should display the when there are jobs', () => {
const { component, exists } = initTestBed({ hasJobs: true });
- expect(exists('jobListLoading')).toBeFalsy();
+ expect(exists('sectionLoading')).toBeFalsy();
expect(component.find('JobTable').length).toBeTruthy();
});
@@ -71,21 +80,20 @@ describe(' ', () => {
},
});
- it('should display a callout with the status and the message', () => {
+ it('should display an error with the status and the message', () => {
expect(exists('jobListError')).toBeTruthy();
expect(find('jobListError').find('EuiText').text()).toEqual('400 Houston we got a problem.');
});
});
describe('when the user does not have the permission to access it', () => {
- const { exists } = initTestBed({ jobLoadError: { status: 403 } });
+ const { exists, find } = initTestBed({ jobLoadError: { status: 403 } });
- it('should render a callout message', () => {
+ it('should render an error message', () => {
expect(exists('jobListNoPermission')).toBeTruthy();
- });
-
- it('should display the page header', () => {
- expect(exists('jobListPageHeader')).toBeTruthy();
+ expect(find('jobListNoPermission').find('EuiText').text()).toEqual(
+ 'You do not have permission to view or add rollup jobs.'
+ );
});
});
});
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
index fe3d2cbd4cbe0..83135cf219f35 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -28,10 +28,11 @@ import {
EuiTableRowCellCheckbox,
EuiText,
EuiToolTip,
+ EuiButton,
} from '@elastic/eui';
import { UIM_SHOW_DETAILS_CLICK } from '../../../../../common';
-import { METRIC_TYPE } from '../../../services';
+import { METRIC_TYPE, getRouterLinkProps } from '../../../services';
import { trackUiMetric } from '../../../../kibana_services';
import { JobActionMenu, JobStatus } from '../../components';
@@ -346,9 +347,9 @@ export class JobTable extends Component {
const atLeastOneItemSelected = Object.keys(idToSelectedJobMap).length > 0;
return (
-
-
- {atLeastOneItemSelected ? (
+
+
+ {atLeastOneItemSelected && (
- ) : null}
+ )}
+
+
+
+
+
@@ -409,7 +418,7 @@ export class JobTable extends Component {
{jobs.length > 0 ? this.renderPager() : null}
-
+
);
}
}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js
index 3fa879923c40a..d52f3fa35a544 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js
@@ -20,6 +20,14 @@ jest.mock('../../../../kibana_services', () => {
};
});
+jest.mock('../../../services', () => {
+ const services = jest.requireActual('../../../services');
+ return {
+ ...services,
+ getRouterLinkProps: (link) => ({ href: link }),
+ };
+});
+
const defaultProps = {
jobs: [],
pager: new Pager(20, 10, 1),
diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js
index 0dc3a02d3c077..c63d01f3c200d 100644
--- a/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js
+++ b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js
@@ -5,9 +5,7 @@
* 2.0.
*/
-import { i18n } from '@kbn/i18n';
-
-import { loadJobs as sendLoadJobsRequest, deserializeJobs, showApiError } from '../../services';
+import { loadJobs as sendLoadJobsRequest, deserializeJobs } from '../../services';
import { LOAD_JOBS_START, LOAD_JOBS_SUCCESS, LOAD_JOBS_FAILURE } from '../action_types';
export const loadJobs = () => async (dispatch) => {
@@ -19,17 +17,10 @@ export const loadJobs = () => async (dispatch) => {
try {
jobs = await sendLoadJobsRequest();
} catch (error) {
- dispatch({
+ return dispatch({
type: LOAD_JOBS_FAILURE,
payload: { error },
});
-
- return showApiError(
- error,
- i18n.translate('xpack.rollupJobs.loadAction.errorTitle', {
- defaultMessage: 'Error loading rollup jobs',
- })
- );
}
dispatch({
diff --git a/x-pack/plugins/rollup/public/shared_imports.ts b/x-pack/plugins/rollup/public/shared_imports.ts
index fd28175318666..c8d7f1d9f13f3 100644
--- a/x-pack/plugins/rollup/public/shared_imports.ts
+++ b/x-pack/plugins/rollup/public/shared_imports.ts
@@ -5,4 +5,8 @@
* 2.0.
*/
-export { extractQueryParams, indices } from '../../../../src/plugins/es_ui_shared/public';
+export {
+ extractQueryParams,
+ indices,
+ SectionLoading,
+} from '../../../../src/plugins/es_ui_shared/public';
diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js
index fa1a786bc8a71..46ddfbcfc2de5 100644
--- a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js
+++ b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js
@@ -5,10 +5,10 @@
* 2.0.
*/
-import { getRouter, setHttp } from '../../crud_app/services';
+import { getRouter, setHttp, init as initDocumentation } from '../../crud_app/services';
import { mockHttpRequest, pageHelpers, nextTick } from './helpers';
import { JOBS } from './helpers/constants';
-import { coreMock } from '../../../../../../src/core/public/mocks';
+import { coreMock, docLinksServiceMock } from '../../../../../../src/core/public/mocks';
jest.mock('../../crud_app/services', () => {
const services = jest.requireActual('../../crud_app/services');
@@ -38,6 +38,7 @@ describe(' ', () => {
beforeAll(() => {
startMock = coreMock.createStart();
setHttp(startMock.http);
+ initDocumentation(docLinksServiceMock.createStartContract());
});
beforeEach(async () => {
diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js
index cfb63893ee423..3987e18538e57 100644
--- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js
+++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js
@@ -24,6 +24,15 @@ jest.mock('../../kibana_services', () => {
};
});
+jest.mock('../../crud_app/services/documentation_links', () => {
+ const coreMocks = jest.requireActual('../../../../../../src/core/public/mocks');
+
+ return {
+ init: jest.fn(),
+ documentationLinks: coreMocks.docLinksServiceMock.createStartContract().links,
+ };
+});
+
const { setup } = pageHelpers.jobList;
describe('Smoke test cloning an existing rollup job from job list', () => {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 9520c1ad0d9c1..91277403d9e05 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18006,7 +18006,6 @@
"xpack.rollupJobs.jobTable.selectRow": "この行 {id} を選択",
"xpack.rollupJobs.licenseCheckErrorMessage": "ライセンス確認失敗",
"xpack.rollupJobs.listBreadcrumbTitle": "ロールアップジョブ",
- "xpack.rollupJobs.loadAction.errorTitle": "ロールアップジョブを読み込み中にエラーが発生",
"xpack.rollupJobs.refreshAction.errorTitle": "ロールアップジョブの更新中にエラーが発生",
"xpack.rollupJobs.rollupIndexPatternsDescription": "ロールアップインデックスを捕捉するインデックスパターンの作成を有効にします。\n それによりロールアップデータに基づくビジュアライゼーションが可能になります。",
"xpack.rollupJobs.rollupIndexPatternsTitle": "ロールアップインデックスパターンを有効にする",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index f74d27eb8b214..632c502d4ef55 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18250,7 +18250,6 @@
"xpack.rollupJobs.jobTable.selectRow": "选择行 {id}",
"xpack.rollupJobs.licenseCheckErrorMessage": "许可证检查失败",
"xpack.rollupJobs.listBreadcrumbTitle": "汇总/打包作业",
- "xpack.rollupJobs.loadAction.errorTitle": "加载汇总/打包作业时出错",
"xpack.rollupJobs.refreshAction.errorTitle": "刷新汇总/打包作业时出错",
"xpack.rollupJobs.rollupIndexPatternsDescription": "启用用于捕获汇总/打包索引的索引模式的创建,\n 汇总/打包索引反过来基于汇总/打包数据启用可视化。",
"xpack.rollupJobs.rollupIndexPatternsTitle": "启用汇总索引模式",
From 953a464e94ad1791c228fc1705430d11964da909 Mon Sep 17 00:00:00 2001
From: Lisa Cawley
Date: Tue, 22 Jun 2021 11:21:19 -0700
Subject: [PATCH 02/78] [Monitoring] Update Kibana rules/alerts language in
setup mode (#102441)
---
x-pack/plugins/monitoring/public/alerts/badge.tsx | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx
index 8b4075ba67cdc..44af8b3327975 100644
--- a/x-pack/plugins/monitoring/public/alerts/badge.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx
@@ -19,13 +19,18 @@ import { getAlertPanelsByCategory } from './lib/get_alert_panels_by_category';
import { getAlertPanelsByNode } from './lib/get_alert_panels_by_node';
export const numberOfAlertsLabel = (count: number) => `${count} alert${count > 1 ? 's' : ''}`;
+export const numberOfRulesLabel = (count: number) => `${count} rule${count > 1 ? 's' : ''}`;
const MAX_TO_SHOW_BY_CATEGORY = 8;
-const PANEL_TITLE = i18n.translate('xpack.monitoring.alerts.badge.panelTitle', {
+const PANEL_TITLE_ALERTS = i18n.translate('xpack.monitoring.alerts.badge.panelTitle', {
defaultMessage: 'Alerts',
});
+const PANEL_TITLE_RULES = i18n.translate('xpack.monitoring.rules.badge.panelTitle', {
+ defaultMessage: 'Rules',
+});
+
const GROUP_BY_NODE = i18n.translate('xpack.monitoring.alerts.badge.groupByNode', {
defaultMessage: 'Group by node',
});
@@ -54,6 +59,7 @@ export const AlertsBadge: React.FC = (props: Props) => {
const [showByNode, setShowByNode] = React.useState(
!inSetupMode && alertCount > MAX_TO_SHOW_BY_CATEGORY
);
+ const PANEL_TITLE = inSetupMode ? PANEL_TITLE_RULES : PANEL_TITLE_ALERTS;
React.useEffect(() => {
if (inSetupMode && showByNode) {
@@ -93,10 +99,12 @@ export const AlertsBadge: React.FC = (props: Props) => {
setShowPopover(true)}
>
- {numberOfAlertsLabel(alertCount)}
+ {inSetupMode ? numberOfRulesLabel(alertCount) : numberOfAlertsLabel(alertCount)}
);
From 00a6bdd4010b2419229e3b9e98c738b10659df52 Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Tue, 22 Jun 2021 14:36:04 -0400
Subject: [PATCH 03/78] Allow initialNamespaces to be used for isolated types
(#102585)
---
docs/api/saved-objects/bulk_create.asciidoc | 5 +
docs/api/saved-objects/create.asciidoc | 5 +
...jectsbulkcreateobject.initialnamespaces.md | 2 +-
...ore-server.savedobjectsbulkcreateobject.md | 2 +-
...dobjectscreateoptions.initialnamespaces.md | 2 +-
...n-core-server.savedobjectscreateoptions.md | 2 +-
.../service/lib/repository.test.js | 145 +++++++++++++-----
.../saved_objects/service/lib/repository.ts | 74 +++++----
.../service/saved_objects_client.ts | 12 +-
src/core/server/server.api.md | 2 +-
.../common/lib/saved_object_test_utils.ts | 6 +-
.../common/suites/bulk_create.ts | 22 ++-
.../common/suites/create.ts | 22 ++-
.../security_and_spaces/apis/bulk_create.ts | 19 ++-
.../security_and_spaces/apis/create.ts | 19 ++-
.../security_only/apis/bulk_create.ts | 18 ++-
.../security_only/apis/create.ts | 18 ++-
.../spaces_only/apis/bulk_create.ts | 18 ++-
.../spaces_only/apis/create.ts | 18 ++-
19 files changed, 307 insertions(+), 104 deletions(-)
diff --git a/docs/api/saved-objects/bulk_create.asciidoc b/docs/api/saved-objects/bulk_create.asciidoc
index 267ab3891d700..5bd3a7587dde9 100644
--- a/docs/api/saved-objects/bulk_create.asciidoc
+++ b/docs/api/saved-objects/bulk_create.asciidoc
@@ -45,6 +45,11 @@ experimental[] Create multiple {kib} saved objects.
(Optional, string array) Identifiers for the <> in which this object is created. If this is provided, the
object is created only in the explicitly defined spaces. If this is not provided, the object is created in the current space
(default behavior).
+* For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces, including
+the "All spaces" identifier (`'*'`).
+* For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only be
+used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed.
+* For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
`version`::
(Optional, number) Specifies the version.
diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc
index d7a368034ef07..e7e25c7d3bba6 100644
--- a/docs/api/saved-objects/create.asciidoc
+++ b/docs/api/saved-objects/create.asciidoc
@@ -52,6 +52,11 @@ any data that you send to the API is properly formed.
(Optional, string array) Identifiers for the <> in which this object is created. If this is provided, the
object is created only in the explicitly defined spaces. If this is not provided, the object is created in the current space
(default behavior).
+* For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces, including
+the "All spaces" identifier (`'*'`).
+* For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only be
+used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed.
+* For global object types (registered with `namespaceType: 'agnostic'): this option cannot be used.
[[saved-objects-api-create-request-codes]]
==== Response code
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md
index 3db8bbadfbd6b..4d094ecde7a96 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md
@@ -6,7 +6,7 @@
Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).
-Note: this can only be used for multi-namespace object types.
+\* For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces, including the "All spaces" identifier (`'*'`). \* For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only be used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed. \* For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
index 6fc01212a2e41..463c3fe81b702 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
@@ -18,7 +18,7 @@ export interface SavedObjectsBulkCreateObject
| [attributes](./kibana-plugin-core-server.savedobjectsbulkcreateobject.attributes.md) | T
| |
| [coreMigrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md) | string
| A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. |
| [id](./kibana-plugin-core-server.savedobjectsbulkcreateobject.id.md) | string
| |
-| [initialNamespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md) | string[]
| Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. |
+| [initialNamespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md) | string[]
| Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).\* For shareable object types (registered with namespaceType: 'multiple'
): this option can be used to specify one or more spaces, including the "All spaces" identifier ('*'
). \* For isolated object types (registered with namespaceType: 'single'
or namespaceType: 'multiple-isolated'
): this option can only be used to specify a single space, and the "All spaces" identifier ('*'
) is not allowed. \* For global object types (registered with namespaceType: 'agnostic'
): this option cannot be used. |
| [migrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.migrationversion.md) | SavedObjectsMigrationVersion
| Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
| [originId](./kibana-plugin-core-server.savedobjectsbulkcreateobject.originid.md) | string
| Optional ID of the original saved object, if this object's id
was regenerated |
| [references](./kibana-plugin-core-server.savedobjectsbulkcreateobject.references.md) | SavedObjectReference[]
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md
index 262b0997cb905..43489b8d2e8a2 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md
@@ -6,7 +6,7 @@
Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).
-Note: this can only be used for multi-namespace object types.
+\* For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces, including the "All spaces" identifier (`'*'`). \* For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only be used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed. \* For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
index 1805f389d4e7f..7eaa9c51f5c82 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
@@ -17,7 +17,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions
| --- | --- | --- |
| [coreMigrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md) | string
| A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. |
| [id](./kibana-plugin-core-server.savedobjectscreateoptions.id.md) | string
| (not recommended) Specify an id for the document |
-| [initialNamespaces](./kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md) | string[]
| Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. |
+| [initialNamespaces](./kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md) | string[]
| Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).\* For shareable object types (registered with namespaceType: 'multiple'
): this option can be used to specify one or more spaces, including the "All spaces" identifier ('*'
). \* For isolated object types (registered with namespaceType: 'single'
or namespaceType: 'multiple-isolated'
): this option can only be used to specify a single space, and the "All spaces" identifier ('*'
) is not allowed. \* For global object types (registered with namespaceType: 'agnostic'
): this option cannot be used. |
| [migrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion
| Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
| [originId](./kibana-plugin-core-server.savedobjectscreateoptions.originid.md) | string
| Optional ID of the original saved object, if this object's id
was regenerated |
| [overwrite](./kibana-plugin-core-server.savedobjectscreateoptions.overwrite.md) | boolean
| Overwrite existing documents (defaults to false) |
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 22c40a547f419..4456784fdbc0b 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -525,15 +525,22 @@ describe('SavedObjectsRepository', () => {
const ns2 = 'bar-namespace';
const ns3 = 'baz-namespace';
const objects = [
- { ...obj1, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [ns2] },
- { ...obj2, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [ns3] },
+ { ...obj1, type: 'dashboard', initialNamespaces: [ns2] },
+ { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, initialNamespaces: [ns2] },
+ { ...obj1, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [ns2, ns3] },
];
await bulkCreateSuccess(objects, { namespace, overwrite: true });
const body = [
- expect.any(Object),
+ { index: expect.objectContaining({ _id: `${ns2}:dashboard:${obj1.id}` }) },
+ expect.objectContaining({ namespace: ns2 }),
+ {
+ index: expect.objectContaining({
+ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj1.id}`,
+ }),
+ },
expect.objectContaining({ namespaces: [ns2] }),
- expect.any(Object),
- expect.objectContaining({ namespaces: [ns3] }),
+ { index: expect.objectContaining({ _id: `${MULTI_NAMESPACE_TYPE}:${obj1.id}` }) },
+ expect.objectContaining({ namespaces: [ns2, ns3] }),
];
expect(client.bulk).toHaveBeenCalledWith(
expect.objectContaining({ body }),
@@ -649,24 +656,19 @@ describe('SavedObjectsRepository', () => {
).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"'));
});
- it(`returns error when initialNamespaces is used with a non-shareable object`, async () => {
- const test = async (objType) => {
- const obj = { ...obj3, type: objType, initialNamespaces: [] };
- await bulkCreateError(
+ it(`returns error when initialNamespaces is used with a space-agnostic object`, async () => {
+ const obj = { ...obj3, type: NAMESPACE_AGNOSTIC_TYPE, initialNamespaces: [] };
+ await bulkCreateError(
+ obj,
+ undefined,
+ expectErrorResult(
obj,
- undefined,
- expectErrorResult(
- obj,
- createBadRequestError('"initialNamespaces" can only be used on multi-namespace types')
- )
- );
- };
- await test('dashboard');
- await test(NAMESPACE_AGNOSTIC_TYPE);
- await test(MULTI_NAMESPACE_ISOLATED_TYPE);
+ createBadRequestError('"initialNamespaces" cannot be used on space-agnostic types')
+ )
+ );
});
- it(`throws when options.initialNamespaces is used with a shareable type and is empty`, async () => {
+ it(`returns error when initialNamespaces is empty`, async () => {
const obj = { ...obj3, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [] };
await bulkCreateError(
obj,
@@ -678,6 +680,26 @@ describe('SavedObjectsRepository', () => {
);
});
+ it(`returns error when initialNamespaces is used with a space-isolated object and does not specify a single space`, async () => {
+ const doTest = async (objType, initialNamespaces) => {
+ const obj = { ...obj3, type: objType, initialNamespaces };
+ await bulkCreateError(
+ obj,
+ undefined,
+ expectErrorResult(
+ obj,
+ createBadRequestError(
+ '"initialNamespaces" can only specify a single space when used with space-isolated types'
+ )
+ )
+ );
+ };
+ await doTest('dashboard', ['spacex', 'spacey']);
+ await doTest('dashboard', ['*']);
+ await doTest(MULTI_NAMESPACE_ISOLATED_TYPE, ['spacex', 'spacey']);
+ await doTest(MULTI_NAMESPACE_ISOLATED_TYPE, ['*']);
+ });
+
it(`returns error when type is invalid`, async () => {
const obj = { ...obj3, type: 'unknownType' };
await bulkCreateError(obj, undefined, expectErrorInvalidType(obj));
@@ -1865,12 +1887,46 @@ describe('SavedObjectsRepository', () => {
});
it(`adds initialNamespaces instead of namespace`, async () => {
- const options = { id, namespace, initialNamespaces: ['bar-namespace', 'baz-namespace'] };
- await createSuccess(MULTI_NAMESPACE_TYPE, attributes, options);
- expect(client.create).toHaveBeenCalledWith(
+ const ns2 = 'bar-namespace';
+ const ns3 = 'baz-namespace';
+ await savedObjectsRepository.create('dashboard', attributes, {
+ id,
+ namespace,
+ initialNamespaces: [ns2],
+ });
+ await savedObjectsRepository.create(MULTI_NAMESPACE_ISOLATED_TYPE, attributes, {
+ id,
+ namespace,
+ initialNamespaces: [ns2],
+ });
+ await savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, {
+ id,
+ namespace,
+ initialNamespaces: [ns2, ns3],
+ });
+
+ expect(client.create).toHaveBeenCalledTimes(3);
+ expect(client.create).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ id: `${ns2}:dashboard:${id}`,
+ body: expect.objectContaining({ namespace: ns2 }),
+ }),
+ expect.anything()
+ );
+ expect(client.create).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({
+ id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${id}`,
+ body: expect.objectContaining({ namespaces: [ns2] }),
+ }),
+ expect.anything()
+ );
+ expect(client.create).toHaveBeenNthCalledWith(
+ 3,
expect.objectContaining({
id: `${MULTI_NAMESPACE_TYPE}:${id}`,
- body: expect.objectContaining({ namespaces: options.initialNamespaces }),
+ body: expect.objectContaining({ namespaces: [ns2, ns3] }),
}),
expect.anything()
);
@@ -1892,29 +1948,40 @@ describe('SavedObjectsRepository', () => {
});
describe('errors', () => {
- it(`throws when options.initialNamespaces is used with a non-shareable object`, async () => {
- const test = async (objType) => {
- await expect(
- savedObjectsRepository.create(objType, attributes, { initialNamespaces: [namespace] })
- ).rejects.toThrowError(
- createBadRequestError(
- '"options.initialNamespaces" can only be used on multi-namespace types'
- )
- );
- };
- await test('dashboard');
- await test(MULTI_NAMESPACE_ISOLATED_TYPE);
- await test(NAMESPACE_AGNOSTIC_TYPE);
+ it(`throws when options.initialNamespaces is used with a space-agnostic object`, async () => {
+ await expect(
+ savedObjectsRepository.create(NAMESPACE_AGNOSTIC_TYPE, attributes, {
+ initialNamespaces: [namespace],
+ })
+ ).rejects.toThrowError(
+ createBadRequestError('"initialNamespaces" cannot be used on space-agnostic types')
+ );
});
- it(`throws when options.initialNamespaces is used with a shareable type and is empty`, async () => {
+ it(`throws when options.initialNamespaces is empty`, async () => {
await expect(
savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, { initialNamespaces: [] })
).rejects.toThrowError(
- createBadRequestError('"options.initialNamespaces" must be a non-empty array of strings')
+ createBadRequestError('"initialNamespaces" must be a non-empty array of strings')
);
});
+ it(`throws when options.initialNamespaces is used with a space-isolated object and does not specify a single space`, async () => {
+ const doTest = async (objType, initialNamespaces) => {
+ await expect(
+ savedObjectsRepository.create(objType, attributes, { initialNamespaces })
+ ).rejects.toThrowError(
+ createBadRequestError(
+ '"initialNamespaces" can only specify a single space when used with space-isolated types'
+ )
+ );
+ };
+ await doTest('dashboard', ['spacex', 'spacey']);
+ await doTest('dashboard', ['*']);
+ await doTest(MULTI_NAMESPACE_ISOLATED_TYPE, ['spacex', 'spacey']);
+ await doTest(MULTI_NAMESPACE_ISOLATED_TYPE, ['*']);
+ });
+
it(`throws when options.namespace is '*'`, async () => {
await expect(
savedObjectsRepository.create(type, attributes, { namespace: ALL_NAMESPACES_STRING })
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index 1577f773434b9..c9fa50da55df1 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -283,28 +283,18 @@ export class SavedObjectsRepository {
} = options;
const namespace = normalizeNamespace(options.namespace);
- if (initialNamespaces) {
- if (!this._registry.isShareable(type)) {
- throw SavedObjectsErrorHelpers.createBadRequestError(
- '"options.initialNamespaces" can only be used on multi-namespace types'
- );
- } else if (!initialNamespaces.length) {
- throw SavedObjectsErrorHelpers.createBadRequestError(
- '"options.initialNamespaces" must be a non-empty array of strings'
- );
- }
- }
+ this.validateInitialNamespaces(type, initialNamespaces);
if (!this._allowedTypes.includes(type)) {
throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type);
}
const time = this._getCurrentTime();
- let savedObjectNamespace;
+ let savedObjectNamespace: string | undefined;
let savedObjectNamespaces: string[] | undefined;
- if (this._registry.isSingleNamespace(type) && namespace) {
- savedObjectNamespace = namespace;
+ if (this._registry.isSingleNamespace(type)) {
+ savedObjectNamespace = initialNamespaces ? initialNamespaces[0] : namespace;
} else if (this._registry.isMultiNamespace(type)) {
if (id && overwrite) {
// we will overwrite a multi-namespace saved object if it exists; if that happens, ensure we preserve its included namespaces
@@ -369,32 +359,29 @@ export class SavedObjectsRepository {
let bulkGetRequestIndexCounter = 0;
const expectedResults: Either[] = objects.map((object) => {
+ const { type, id, initialNamespaces } = object;
let error: DecoratedError | undefined;
- if (!this._allowedTypes.includes(object.type)) {
- error = SavedObjectsErrorHelpers.createUnsupportedTypeError(object.type);
- } else if (object.initialNamespaces) {
- if (!this._registry.isShareable(object.type)) {
- error = SavedObjectsErrorHelpers.createBadRequestError(
- '"initialNamespaces" can only be used on multi-namespace types'
- );
- } else if (!object.initialNamespaces.length) {
- error = SavedObjectsErrorHelpers.createBadRequestError(
- '"initialNamespaces" must be a non-empty array of strings'
- );
+ if (!this._allowedTypes.includes(type)) {
+ error = SavedObjectsErrorHelpers.createUnsupportedTypeError(type);
+ } else {
+ try {
+ this.validateInitialNamespaces(type, initialNamespaces);
+ } catch (e) {
+ error = e;
}
}
if (error) {
return {
tag: 'Left' as 'Left',
- error: { id: object.id, type: object.type, error: errorContent(error) },
+ error: { id, type, error: errorContent(error) },
};
}
- const method = object.id && overwrite ? 'index' : 'create';
- const requiresNamespacesCheck = object.id && this._registry.isMultiNamespace(object.type);
+ const method = id && overwrite ? 'index' : 'create';
+ const requiresNamespacesCheck = id && this._registry.isMultiNamespace(type);
- if (object.id == null) {
+ if (id == null) {
object.id = SavedObjectsUtils.generateId();
}
@@ -434,8 +421,8 @@ export class SavedObjectsRepository {
return expectedBulkGetResult;
}
- let savedObjectNamespace;
- let savedObjectNamespaces;
+ let savedObjectNamespace: string | undefined;
+ let savedObjectNamespaces: string[] | undefined;
let versionProperties;
const {
esRequestIndex,
@@ -469,7 +456,7 @@ export class SavedObjectsRepository {
versionProperties = getExpectedVersionProperties(version, actualResult);
} else {
if (this._registry.isSingleNamespace(object.type)) {
- savedObjectNamespace = namespace;
+ savedObjectNamespace = initialNamespaces ? initialNamespaces[0] : namespace;
} else if (this._registry.isMultiNamespace(object.type)) {
savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace);
}
@@ -2080,6 +2067,29 @@ export class SavedObjectsRepository {
const object = await this.get(type, id, options);
return { saved_object: object, outcome: 'exactMatch' };
}
+
+ private validateInitialNamespaces(type: string, initialNamespaces: string[] | undefined) {
+ if (!initialNamespaces) {
+ return;
+ }
+
+ if (this._registry.isNamespaceAgnostic(type)) {
+ throw SavedObjectsErrorHelpers.createBadRequestError(
+ '"initialNamespaces" cannot be used on space-agnostic types'
+ );
+ } else if (!initialNamespaces.length) {
+ throw SavedObjectsErrorHelpers.createBadRequestError(
+ '"initialNamespaces" must be a non-empty array of strings'
+ );
+ } else if (
+ !this._registry.isShareable(type) &&
+ (initialNamespaces.length > 1 || initialNamespaces.includes(ALL_NAMESPACES_STRING))
+ ) {
+ throw SavedObjectsErrorHelpers.createBadRequestError(
+ '"initialNamespaces" can only specify a single space when used with space-isolated types'
+ );
+ }
+ }
}
/**
diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts
index af682cfb81296..1423050145695 100644
--- a/src/core/server/saved_objects/service/saved_objects_client.ts
+++ b/src/core/server/saved_objects/service/saved_objects_client.ts
@@ -63,7 +63,11 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
* Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in
* {@link SavedObjectsCreateOptions}.
*
- * Note: this can only be used for multi-namespace object types.
+ * * For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces,
+ * including the "All spaces" identifier (`'*'`).
+ * * For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only
+ * be used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed.
+ * * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
*/
initialNamespaces?: string[];
}
@@ -96,7 +100,11 @@ export interface SavedObjectsBulkCreateObject {
* Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in
* {@link SavedObjectsCreateOptions}.
*
- * Note: this can only be used for multi-namespace object types.
+ * * For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces,
+ * including the "All spaces" identifier (`'*'`).
+ * * For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only
+ * be used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed.
+ * * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
*/
initialNamespaces?: string[];
}
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 9e7721fde90e7..fcecf39f7e53a 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -2901,7 +2901,7 @@ export class SavedObjectsRepository {
resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>;
updateObjectsSpaces(objects: SavedObjectsUpdateObjectsSpacesObject[], spacesToAdd: string[], spacesToRemove: string[], options?: SavedObjectsUpdateObjectsSpacesOptions): Promise;
-}
+ }
// @public
export interface SavedObjectsRepositoryFactory {
diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts
index b712c2882ee0f..eb0c161049cf0 100644
--- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts
+++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts
@@ -154,12 +154,14 @@ export const expectResponses = {
// bulk request error
expect(object.type).to.eql(type);
expect(object.id).to.eql(id);
- expect(object.error).to.eql(error.output.payload);
+ expect(object.error.error).to.eql(error.output.payload.error);
+ expect(object.error.statusCode).to.eql(error.output.payload.statusCode);
+ // ignore the error.message, because it can vary for decorated errors
} else {
// non-bulk request error
expect(object.error).to.eql(error.output.payload.error);
expect(object.statusCode).to.eql(error.output.payload.statusCode);
- // ignore the error.message, because it can vary for decorated non-bulk errors (e.g., conflict)
+ // ignore the error.message, because it can vary for decorated errors
}
} else {
// fall back to default behavior of testing the success outcome
diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts
index 5860ec1f193b2..06758da1ebad2 100644
--- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts
+++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts
@@ -41,13 +41,25 @@ const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID];
const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: 'new-dashboard-id' });
const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' });
-const NEW_EACH_SPACE_OBJ = Object.freeze({
+const INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE = Object.freeze({
+ type: 'isolatedtype',
+ id: 'new-other-space-id',
+ expectedNamespaces: ['other-space'], // expected namespaces of resulting object
+ initialNamespaces: ['other-space'], // args passed to the bulkCreate method
+});
+const INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE = Object.freeze({
+ type: 'sharecapabletype',
+ id: 'new-other-space-id',
+ expectedNamespaces: ['other-space'], // expected namespaces of resulting object
+ initialNamespaces: ['other-space'], // args passed to the bulkCreate method
+});
+const INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE = Object.freeze({
type: 'sharedtype',
id: 'new-each-space-id',
expectedNamespaces: EACH_SPACE, // expected namespaces of resulting object
initialNamespaces: EACH_SPACE, // args passed to the bulkCreate method
});
-const NEW_ALL_SPACES_OBJ = Object.freeze({
+const INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES = Object.freeze({
type: 'sharedtype',
id: 'new-all-spaces-id',
expectedNamespaces: [ALL_SPACES_ID], // expected namespaces of resulting object
@@ -58,8 +70,10 @@ export const TEST_CASES: Record = Object.freeze({
...CASES,
NEW_SINGLE_NAMESPACE_OBJ,
NEW_MULTI_NAMESPACE_OBJ,
- NEW_EACH_SPACE_OBJ,
- NEW_ALL_SPACES_OBJ,
+ INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
NEW_NAMESPACE_AGNOSTIC_OBJ,
});
diff --git a/x-pack/test/saved_object_api_integration/common/suites/create.ts b/x-pack/test/saved_object_api_integration/common/suites/create.ts
index ff2bfdefb4c08..298e1a9807175 100644
--- a/x-pack/test/saved_object_api_integration/common/suites/create.ts
+++ b/x-pack/test/saved_object_api_integration/common/suites/create.ts
@@ -41,13 +41,25 @@ const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID];
// we could create six separate test cases to test every permutation, but there's no real value in doing so
const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: '' });
const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' });
-const NEW_EACH_SPACE_OBJ = Object.freeze({
+const INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE = Object.freeze({
+ type: 'isolatedtype',
+ id: 'new-other-space-id',
+ expectedNamespaces: ['other-space'], // expected namespaces of resulting object
+ initialNamespaces: ['other-space'], // args passed to the bulkCreate method
+});
+const INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE = Object.freeze({
+ type: 'sharecapabletype',
+ id: 'new-other-space-id',
+ expectedNamespaces: ['other-space'], // expected namespaces of resulting object
+ initialNamespaces: ['other-space'], // args passed to the bulkCreate method
+});
+const INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE = Object.freeze({
type: 'sharedtype',
id: 'new-each-space-id',
expectedNamespaces: EACH_SPACE, // expected namespaces of resulting object
initialNamespaces: EACH_SPACE, // args passed to the bulkCreate method
});
-const NEW_ALL_SPACES_OBJ = Object.freeze({
+const INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES = Object.freeze({
type: 'sharedtype',
id: 'new-all-spaces-id',
expectedNamespaces: [ALL_SPACES_ID], // expected namespaces of resulting object
@@ -58,8 +70,10 @@ export const TEST_CASES: Record = Object.freeze({
...CASES,
NEW_SINGLE_NAMESPACE_OBJ,
NEW_MULTI_NAMESPACE_OBJ,
- NEW_EACH_SPACE_OBJ,
- NEW_ALL_SPACES_OBJ,
+ INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
NEW_NAMESPACE_AGNOSTIC_OBJ,
});
diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts
index 1fa24c6d6e2d6..e048a4abc8ccc 100644
--- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts
+++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -75,7 +75,22 @@ const createTestCases = (overwrite: boolean, spaceId: string) => {
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
];
- const crossNamespace = [CASES.NEW_EACH_SPACE_OBJ, CASES.NEW_ALL_SPACES_OBJ];
+ const crossNamespace = [
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
+ ];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, crossNamespace, hiddenType, allTypes };
diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts
index 3553ae0e5b538..8215c991a9287 100644
--- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts
+++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -62,7 +62,22 @@ const createTestCases = (overwrite: boolean, spaceId: string) => {
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
];
- const crossNamespace = [CASES.NEW_EACH_SPACE_OBJ, CASES.NEW_ALL_SPACES_OBJ];
+ const crossNamespace = [
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
+ ];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(crossNamespace, hiddenType);
return { normalTypes, crossNamespace, hiddenType, allTypes };
diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts
index 7487466f4b38c..f9423d77c5bb5 100644
--- a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts
+++ b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -39,8 +39,20 @@ const createTestCases = (overwrite: boolean) => {
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
- CASES.NEW_EACH_SPACE_OBJ,
- CASES.NEW_ALL_SPACES_OBJ,
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts
index 7eda7f5283448..67195637f0c0a 100644
--- a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts
+++ b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -38,8 +38,20 @@ const createTestCases = (overwrite: boolean) => {
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
- CASES.NEW_EACH_SPACE_OBJ,
- CASES.NEW_ALL_SPACES_OBJ,
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts
index 5812aaf43060d..c448d73ce7bf8 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { bulkCreateTestSuiteFactory, TEST_CASES as CASES } from '../../common/suites/bulk_create';
@@ -70,8 +70,20 @@ const createTestCases = (overwrite: boolean, spaceId: string) => {
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
- CASES.NEW_EACH_SPACE_OBJ,
- CASES.NEW_ALL_SPACES_OBJ,
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
};
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts
index 4c91781b6ab2c..7c8726896c18a 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SPACES } from '../../common/lib/spaces';
+import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { createTestSuiteFactory, TEST_CASES as CASES } from '../../common/suites/create';
@@ -57,8 +57,20 @@ const createTestCases = (overwrite: boolean, spaceId: string) => {
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
- CASES.NEW_EACH_SPACE_OBJ,
- CASES.NEW_ALL_SPACES_OBJ,
+ {
+ ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
+ initialNamespaces: ['x', 'y'],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ {
+ ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
+ initialNamespaces: [ALL_SPACES_ID],
+ ...fail400(), // cannot be created in multiple spaces
+ },
+ CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
+ CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
};
From f422cbdcf17f5e598e715e88bd9bdd52d2f1d72b Mon Sep 17 00:00:00 2001
From: Constance
Date: Tue, 22 Jun 2021 11:40:10 -0700
Subject: [PATCH 04/78] [App Search] Convert API Logs page to new page template
+ empty state polish (#102820)
* Convert API Logs noItemsMessage to its own empty state prompt
- Will be used by new page template
* Convert API Logs view to new page template
+ use new empty state
+ add tests clarifying loading UX
* Update router
* Fix i18n ID
---
.../components/api_logs/api_logs.test.tsx | 24 +++---
.../components/api_logs/api_logs.tsx | 73 ++++++++-----------
.../components/api_logs_table.test.tsx | 10 +--
.../api_logs/components/api_logs_table.tsx | 20 -----
.../api_logs/components/empty_state.test.tsx | 27 +++++++
.../api_logs/components/empty_state.tsx | 45 ++++++++++++
.../components/api_logs/components/index.ts | 1 +
.../components/engine/engine_router.tsx | 10 +--
8 files changed, 124 insertions(+), 86 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx
index c2a11ec06fa6a..5b082ce8d26ba 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx
@@ -13,10 +13,7 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiPageHeader } from '@elastic/eui';
-
-import { Loading } from '../../../shared/loading';
-import { rerender } from '../../../test_helpers';
+import { rerender, getPageTitle } from '../../../test_helpers';
import { LogRetentionCallout, LogRetentionTooltip } from '../log_retention';
import { ApiLogsTable, NewApiEventsPrompt } from './components';
@@ -42,7 +39,7 @@ describe('ApiLogs', () => {
it('renders', () => {
const wrapper = shallow( );
- expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('API Logs');
+ expect(getPageTitle(wrapper)).toEqual('API Logs');
expect(wrapper.find(ApiLogsTable)).toHaveLength(1);
expect(wrapper.find(NewApiEventsPrompt)).toHaveLength(1);
@@ -50,11 +47,20 @@ describe('ApiLogs', () => {
expect(wrapper.find(LogRetentionTooltip).prop('type')).toEqual('api');
});
- it('renders a loading screen', () => {
- setMockValues({ ...values, dataLoading: true, apiLogs: [] });
- const wrapper = shallow( );
+ describe('loading state', () => {
+ it('renders a full-page loading state on initial page load (no logs exist yet)', () => {
+ setMockValues({ ...values, dataLoading: true, apiLogs: [] });
+ const wrapper = shallow( );
+
+ expect(wrapper.prop('isLoading')).toEqual(true);
+ });
+
+ it('does not re-render a full-page loading state after initial page load (uses component-level loading state instead)', () => {
+ setMockValues({ ...values, dataLoading: true, apiLogs: [{}] });
+ const wrapper = shallow( );
- expect(wrapper.find(Loading)).toHaveLength(1);
+ expect(wrapper.prop('isLoading')).toEqual(false);
+ });
});
describe('effects', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx
index b8179163c93f9..d3eef77db21f0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx
@@ -9,25 +9,14 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
-import {
- EuiPageHeader,
- EuiTitle,
- EuiPageContent,
- EuiPageContentBody,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
-} from '@elastic/eui';
-
-import { FlashMessages } from '../../../shared/flash_messages';
-import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
-import { Loading } from '../../../shared/loading';
+import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui';
import { getEngineBreadcrumbs } from '../engine';
+import { AppSearchPageTemplate } from '../layout';
import { LogRetentionCallout, LogRetentionTooltip, LogRetentionOptions } from '../log_retention';
import { ApiLogFlyout } from './api_log';
-import { ApiLogsTable, NewApiEventsPrompt } from './components';
+import { ApiLogsTable, NewApiEventsPrompt, EmptyState } from './components';
import { API_LOGS_TITLE, RECENT_API_EVENTS } from './constants';
import { ApiLogsLogic } from './';
@@ -44,38 +33,36 @@ export const ApiLogs: React.FC = () => {
pollForApiLogs();
}, []);
- if (dataLoading && !apiLogs.length) return ;
-
return (
- <>
-
-
-
-
+ }
+ >
-
-
-
-
-
- {RECENT_API_EVENTS}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {RECENT_API_EVENTS}
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- >
+
+
+
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx
index 2a00cc6eb42bb..82d3d4715cbc5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiBasicTable, EuiBadge, EuiHealth, EuiButtonEmpty, EuiEmptyPrompt } from '@elastic/eui';
+import { EuiBasicTable, EuiBadge, EuiHealth, EuiButtonEmpty } from '@elastic/eui';
import { DEFAULT_META } from '../../../../shared/constants';
import { mountWithIntl } from '../../../../test_helpers';
@@ -91,14 +91,6 @@ describe('ApiLogsTable', () => {
expect(actions.openFlyout).toHaveBeenCalled();
});
- it('renders an empty prompt if no items are passed', () => {
- setMockValues({ ...values, apiLogs: [] });
- const wrapper = mountWithIntl( );
- const promptContent = wrapper.find(EuiEmptyPrompt).text();
-
- expect(promptContent).toContain('Perform your first API call');
- });
-
describe('hasPagination', () => {
it('does not render with pagination by default', () => {
const wrapper = shallow( );
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx
index bb1327ce2da30..1b5a8084f5b59 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx
@@ -15,7 +15,6 @@ import {
EuiBadge,
EuiHealth,
EuiButtonEmpty,
- EuiEmptyPrompt,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedRelative } from '@kbn/i18n/react';
@@ -109,25 +108,6 @@ export const ApiLogsTable: React.FC = ({ hasPagination }) => {
items={apiLogs}
responsive
loading={dataLoading}
- noItemsMessage={
-
- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle', {
- defaultMessage: 'Perform your first API call',
- })}
-
- }
- body={
-
- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription', {
- defaultMessage: "Check back after you've performed some API calls.",
- })}
-
- }
- />
- }
{...paginationProps}
/>
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx
new file mode 100644
index 0000000000000..3ad22ceac5840
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+
+import { EmptyState } from './';
+
+describe('EmptyState', () => {
+ it('renders', () => {
+ const wrapper = shallow( )
+ .find(EuiEmptyPrompt)
+ .dive();
+
+ expect(wrapper.find('h2').text()).toEqual('Perform your first API call');
+ expect(wrapper.find(EuiButton).prop('href')).toEqual(
+ expect.stringContaining('/api-reference.html')
+ );
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx
new file mode 100644
index 0000000000000..3f6f44adefc71
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { DOCS_PREFIX } from '../../../routes';
+
+export const EmptyState: React.FC = () => (
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle', {
+ defaultMessage: 'Perform your first API call',
+ })}
+
+ }
+ body={
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription', {
+ defaultMessage: "Check back after you've performed some API calls.",
+ })}
+
+ }
+ actions={
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel', {
+ defaultMessage: 'View the API reference',
+ })}
+
+ }
+ />
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts
index c0edc51d06228..863216554a540 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts
@@ -7,3 +7,4 @@
export { ApiLogsTable } from './api_logs_table';
export { NewApiEventsPrompt } from './new_api_events_prompt';
+export { EmptyState } from './empty_state';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
index 3e18c9e680de2..fc057858426d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
@@ -114,6 +114,11 @@ export const EngineRouter: React.FC = () => {
)}
+ {canViewEngineApiLogs && (
+
+
+
+ )}
{/* TODO: Remove layout once page template migration is over */}
}>
{canViewEngineSchema && (
@@ -141,11 +146,6 @@ export const EngineRouter: React.FC = () => {
)}
- {canViewEngineApiLogs && (
-
-
-
- )}
{canViewMetaEngineSourceEngines && (
From 2b0f1256ddd2e65db8f29c198000db4d7cb3af61 Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Tue, 22 Jun 2021 14:11:15 -0500
Subject: [PATCH 05/78] [canvas] New Home Page (#102446)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/canvas/i18n/components.ts | 221 -------
x-pack/plugins/canvas/i18n/errors.ts | 53 +-
.../public/components/home/home.component.tsx | 67 +++
.../public/components/home/home.stories.tsx | 30 +
.../canvas/public/components/home/home.tsx | 33 +
.../public/components/home/hooks/index.ts | 15 +
.../home/hooks/use_clone_workpad.ts | 60 ++
.../home/hooks/use_create_from_template.ts | 32 +
.../home/hooks/use_create_workpad.ts | 46 ++
.../home/hooks/use_delete_workpad.ts | 63 ++
.../home/hooks/use_download_workpad.ts | 12 +
.../home/hooks/use_find_templates.ts | 38 ++
.../components/home/hooks/use_find_workpad.ts | 57 ++
.../home/hooks/use_upload_workpad.ts | 100 ++++
.../index.js => home/index.ts} | 2 +-
.../home/my_workpads/empty_prompt.stories.tsx | 19 +
.../home/my_workpads/empty_prompt.tsx | 65 ++
.../components/home/my_workpads/index.ts | 10 +
.../components/home/my_workpads/loading.tsx | 17 +
.../my_workpads/my_workpads.component.tsx | 38 ++
.../home/my_workpads/my_workpads.stories.tsx | 56 ++
.../home/my_workpads/my_workpads.tsx | 42 ++
.../my_workpads/upload_dropzone.component.tsx | 30 +
.../home/my_workpads/upload_dropzone.scss | 8 +
.../home/my_workpads/upload_dropzone.tsx | 55 ++
.../my_workpads/workpad_import.component.tsx | 40 ++
.../home/my_workpads/workpad_import.tsx | 35 ++
.../my_workpads/workpad_table.component.tsx | 203 +++++++
.../my_workpads/workpad_table.stories.tsx | 83 +++
.../home/my_workpads/workpad_table.tsx | 38 ++
.../workpad_table_tools.component.tsx | 160 +++++
.../home/my_workpads/workpad_table_tools.tsx | 51 ++
.../home/workpad_create.component.tsx | 37 ++
.../public/components/home/workpad_create.tsx | 31 +
.../home/workpad_templates/index.ts | 10 +
.../workpad_templates.component.tsx | 157 +++++
.../workpad_templates.stories.tsx | 62 ++
.../workpad_templates/workpad_templates.tsx | 35 ++
.../home_app/home_app.component.tsx | 18 +-
.../toolbar/__stories__/toolbar.stories.tsx | 2 -
.../components/toolbar/toolbar.component.tsx | 36 +-
.../components/workpad_loader/index.tsx | 173 ------
.../workpad_loader/upload_workpad.js | 52 --
.../workpad_loader/workpad_create.js | 31 -
.../workpad_loader/workpad_dropzone/index.js | 31 -
.../workpad_dropzone/workpad_dropzone.js | 31 -
.../workpad_dropzone/workpad_dropzone.scss | 22 -
.../workpad_loader/workpad_loader.js | 426 -------------
.../workpad_loader/workpad_loader.scss | 25 -
.../workpad_loader/workpad_search.js | 44 --
.../workpad_manager/workpad_manager.js | 69 ---
.../workpad_templates.stories.storyshot | 564 ------------------
.../examples/workpad_templates.stories.tsx | 45 --
.../components/workpad_templates/index.tsx | 86 ---
.../workpad_templates/workpad_templates.tsx | 215 -------
.../canvas/public/lib/get_tags_filter.tsx | 39 --
.../plugins/canvas/public/services/index.ts | 2 +-
.../canvas/public/services/stubs/platform.ts | 8 +-
.../canvas/public/services/stubs/workpad.ts | 96 ++-
.../plugins/canvas/public/services/workpad.ts | 21 +-
x-pack/plugins/canvas/public/style/index.scss | 2 -
.../canvas/storybook/decorators/index.ts | 3 +-
.../storybook/decorators/redux_decorator.tsx | 2 +-
.../decorators/services_decorator.tsx | 40 +-
x-pack/plugins/canvas/storybook/index.ts | 5 +
x-pack/plugins/canvas/storybook/main.ts | 5 +
.../empty_prompt.stories.storyshot | 65 ++
.../canvas/storybook/storyshots.test.tsx | 7 +-
.../translations/translations/ja-JP.json | 81 ++-
.../translations/translations/zh-CN.json | 81 ++-
x-pack/test/accessibility/apps/canvas.ts | 2 +-
.../test/functional/apps/canvas/smoke_test.js | 2 +-
.../functional/page_objects/canvas_page.ts | 2 +-
73 files changed, 2156 insertions(+), 2288 deletions(-)
create mode 100644 x-pack/plugins/canvas/public/components/home/home.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/home.stories.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/home.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/index.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_download_workpad.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts
rename x-pack/plugins/canvas/public/components/{workpad_manager/index.js => home/index.ts} (83%)
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.stories.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/index.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.scss
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_create.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_create.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_templates/index.ts
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.component.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
create mode 100644 x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/index.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/upload_workpad.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.scss
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.scss
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_templates/examples/workpad_templates.stories.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_templates/index.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.tsx
delete mode 100644 x-pack/plugins/canvas/public/lib/get_tags_filter.tsx
create mode 100644 x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts
index 7a23137e7ef60..6f011bb73e3b0 100644
--- a/x-pack/plugins/canvas/i18n/components.ts
+++ b/x-pack/plugins/canvas/i18n/components.ts
@@ -1166,12 +1166,6 @@ export const ComponentStrings = {
description: 'This is referring to the dimensions of U.S. standard letter paper.',
}),
},
- WorkpadCreate: {
- getWorkpadCreateButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadCreate.createButtonLabel', {
- defaultMessage: 'Create workpad',
- }),
- },
WorkpadHeader: {
getAddElementButtonLabel: () =>
i18n.translate('xpack.canvas.workpadHeader.addElementButtonLabel', {
@@ -1546,219 +1540,4 @@ export const ComponentStrings = {
defaultMessage: 'Reset',
}),
},
- WorkpadLoader: {
- getClonedWorkpadName: (workpadName: string) =>
- i18n.translate('xpack.canvas.workpadLoader.clonedWorkpadName', {
- defaultMessage: 'Copy of {workpadName}',
- values: {
- workpadName,
- },
- description:
- 'This suffix is added to the end of the name of a cloned workpad to indicate that this ' +
- 'new workpad is a copy of the original workpad. Example: "Copy of Sales Pitch"',
- }),
- getCloneToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.cloneTooltip', {
- defaultMessage: 'Clone workpad',
- }),
- getCreateWorkpadLoadingDescription: () =>
- i18n.translate('xpack.canvas.workpadLoader.createWorkpadLoadingDescription', {
- defaultMessage: 'Creating workpad...',
- description:
- 'This message appears while the user is waiting for a new workpad to be created',
- }),
- getDeleteButtonAriaLabel: (numberOfWorkpads: number) =>
- i18n.translate('xpack.canvas.workpadLoader.deleteButtonAriaLabel', {
- defaultMessage: 'Delete {numberOfWorkpads} workpads',
- values: {
- numberOfWorkpads,
- },
- }),
- getDeleteButtonLabel: (numberOfWorkpads: number) =>
- i18n.translate('xpack.canvas.workpadLoader.deleteButtonLabel', {
- defaultMessage: 'Delete ({numberOfWorkpads})',
- values: {
- numberOfWorkpads,
- },
- }),
- getDeleteModalConfirmButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel', {
- defaultMessage: 'Delete',
- }),
- getDeleteModalDescription: () =>
- i18n.translate('xpack.canvas.workpadLoader.deleteModalDescription', {
- defaultMessage: `You can't recover deleted workpads.`,
- }),
- getDeleteMultipleWorkpadModalTitle: (numberOfWorkpads: string) =>
- i18n.translate('xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle', {
- defaultMessage: 'Delete {numberOfWorkpads} workpads?',
- values: {
- numberOfWorkpads,
- },
- }),
- getDeleteSingleWorkpadModalTitle: (workpadName: string) =>
- i18n.translate('xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle', {
- defaultMessage: `Delete workpad '{workpadName}'?`,
- values: {
- workpadName,
- },
- }),
- getEmptyPromptGettingStartedDescription: () =>
- i18n.translate('xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription', {
- defaultMessage:
- 'Create a new workpad, start from a template, or import a workpad {JSON} file by dropping it here.',
- values: {
- JSON,
- },
- }),
- getEmptyPromptNewUserDescription: () =>
- i18n.translate('xpack.canvas.workpadLoader.emptyPromptNewUserDescription', {
- defaultMessage: 'New to {CANVAS}?',
- values: {
- CANVAS,
- },
- }),
- getEmptyPromptTitle: () =>
- i18n.translate('xpack.canvas.workpadLoader.emptyPromptTitle', {
- defaultMessage: 'Add your first workpad',
- }),
- getExportButtonAriaLabel: (numberOfWorkpads: number) =>
- i18n.translate('xpack.canvas.workpadLoader.exportButtonAriaLabel', {
- defaultMessage: 'Export {numberOfWorkpads} workpads',
- values: {
- numberOfWorkpads,
- },
- }),
- getExportButtonLabel: (numberOfWorkpads: number) =>
- i18n.translate('xpack.canvas.workpadLoader.exportButtonLabel', {
- defaultMessage: 'Export ({numberOfWorkpads})',
- values: {
- numberOfWorkpads,
- },
- }),
- getExportToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.exportTooltip', {
- defaultMessage: 'Export workpad',
- }),
- getFetchLoadingDescription: () =>
- i18n.translate('xpack.canvas.workpadLoader.fetchLoadingDescription', {
- defaultMessage: 'Fetching workpads...',
- description:
- 'This message appears while the user is waiting for their list of workpads to load',
- }),
- getFilePickerPlaceholder: () =>
- i18n.translate('xpack.canvas.workpadLoader.filePickerPlaceholder', {
- defaultMessage: 'Import workpad {JSON} file',
- values: {
- JSON,
- },
- }),
- getLoadWorkpadArialLabel: (workpadName: string) =>
- i18n.translate('xpack.canvas.workpadLoader.loadWorkpadArialLabel', {
- defaultMessage: `Load workpad '{workpadName}'`,
- values: {
- workpadName,
- },
- }),
- getNoPermissionToCloneToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.noPermissionToCloneToolTip', {
- defaultMessage: `You don't have permission to clone workpads`,
- }),
- getNoPermissionToCreateToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.noPermissionToCreateToolTip', {
- defaultMessage: `You don't have permission to create workpads`,
- }),
- getNoPermissionToDeleteToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.noPermissionToDeleteToolTip', {
- defaultMessage: `You don't have permission to delete workpads`,
- }),
- getNoPermissionToUploadToolTip: () =>
- i18n.translate('xpack.canvas.workpadLoader.noPermissionToUploadToolTip', {
- defaultMessage: `You don't have permission to upload workpads`,
- }),
- getSampleDataLinkLabel: () =>
- i18n.translate('xpack.canvas.workpadLoader.sampleDataLinkLabel', {
- defaultMessage: 'Add your first workpad',
- }),
- getTableCreatedColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadLoader.table.createdColumnTitle', {
- defaultMessage: 'Created',
- description: 'This column in the table contains the date/time the workpad was created.',
- }),
- getTableNameColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadLoader.table.nameColumnTitle', {
- defaultMessage: 'Workpad name',
- }),
- getTableUpdatedColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadLoader.table.updatedColumnTitle', {
- defaultMessage: 'Updated',
- description:
- 'This column in the table contains the date/time the workpad was last updated.',
- }),
- getTableActionsColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadLoader.table.actionsColumnTitle', {
- defaultMessage: 'Actions',
- description:
- 'This column in the table contains the actions that can be taken on a workpad.',
- }),
- },
- WorkpadManager: {
- getModalTitle: () =>
- i18n.translate('xpack.canvas.workpadManager.modalTitle', {
- defaultMessage: '{CANVAS} workpads',
- values: {
- CANVAS,
- },
- }),
- getMyWorkpadsTabLabel: () =>
- i18n.translate('xpack.canvas.workpadManager.myWorkpadsTabLabel', {
- defaultMessage: 'My workpads',
- }),
- getWorkpadTemplatesTabLabel: () =>
- i18n.translate('xpack.canvas.workpadManager.workpadTemplatesTabLabel', {
- defaultMessage: 'Templates',
- description: 'The label for the tab that displays a list of designed workpad templates.',
- }),
- },
- WorkpadSearch: {
- getWorkpadSearchPlaceholder: () =>
- i18n.translate('xpack.canvas.workpadSearch.searchPlaceholder', {
- defaultMessage: 'Find workpad',
- }),
- },
- WorkpadTemplates: {
- getCloneTemplateLinkAriaLabel: (templateName: string) =>
- i18n.translate('xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel', {
- defaultMessage: `Clone workpad template '{templateName}'`,
- values: {
- templateName,
- },
- }),
- getTableDescriptionColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadTemplates.table.descriptionColumnTitle', {
- defaultMessage: 'Description',
- }),
- getTableNameColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadTemplates.table.nameColumnTitle', {
- defaultMessage: 'Template name',
- }),
- getTableTagsColumnTitle: () =>
- i18n.translate('xpack.canvas.workpadTemplates.table.tagsColumnTitle', {
- defaultMessage: 'Tags',
- description:
- 'This column contains relevant tags that indicate what type of template ' +
- 'is displayed. For example: "report", "presentation", etc.',
- }),
- getTemplateSearchPlaceholder: () =>
- i18n.translate('xpack.canvas.workpadTemplate.searchPlaceholder', {
- defaultMessage: 'Find template',
- }),
- getCreatingTemplateLabel: (templateName: string) =>
- i18n.translate('xpack.canvas.workpadTemplate.creatingTemplateLabel', {
- defaultMessage: `Creating from template '{templateName}'`,
- values: {
- templateName,
- },
- }),
- },
};
diff --git a/x-pack/plugins/canvas/i18n/errors.ts b/x-pack/plugins/canvas/i18n/errors.ts
index 0928045119234..a55762dce2d20 100644
--- a/x-pack/plugins/canvas/i18n/errors.ts
+++ b/x-pack/plugins/canvas/i18n/errors.ts
@@ -6,7 +6,6 @@
*/
import { i18n } from '@kbn/i18n';
-import { CANVAS, JSON } from './constants';
export const ErrorStrings = {
actionsElements: {
@@ -93,54 +92,10 @@ export const ErrorStrings = {
},
}),
},
- WorkpadFileUpload: {
- getAcceptJSONOnlyErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage', {
- defaultMessage: 'Only {JSON} files are accepted',
- values: {
- JSON,
- },
- }),
- getFileUploadFailureWithFileNameErrorMessage: (fileName: string) =>
- i18n.translate('xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage', {
- defaultMessage: `Couldn't upload '{fileName}'`,
- values: {
- fileName,
- },
- }),
- getFileUploadFailureWithoutFileNameErrorMessage: () =>
- i18n.translate(
- 'xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage',
- {
- defaultMessage: `Couldn't upload file`,
- }
- ),
- getMissingPropertiesErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage', {
- defaultMessage:
- 'Some properties required for a {CANVAS} workpad are missing. Edit your {JSON} file to provide the correct property values, and try again.',
- values: {
- CANVAS,
- JSON,
- },
- }),
- },
- WorkpadLoader: {
- getCloneFailureErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadLoader.cloneFailureErrorMessage', {
- defaultMessage: `Couldn't clone workpad`,
- }),
- getDeleteFailureErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadLoader.deleteFailureErrorMessage', {
- defaultMessage: `Couldn't delete all workpads`,
- }),
- getFindFailureErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadLoader.findFailureErrorMessage', {
- defaultMessage: `Couldn't find workpad`,
- }),
- getUploadFailureErrorMessage: () =>
- i18n.translate('xpack.canvas.error.workpadLoader.uploadFailureErrorMessage', {
- defaultMessage: `Couldn't upload workpad`,
+ WorkpadDropzone: {
+ getTooManyFilesErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.workpadDropzone.tooManyFilesErrorMessage', {
+ defaultMessage: 'One one file can be uploaded at a time',
}),
},
workpadRoutes: {
diff --git a/x-pack/plugins/canvas/public/components/home/home.component.tsx b/x-pack/plugins/canvas/public/components/home/home.component.tsx
new file mode 100644
index 0000000000000..96a773186da2b
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/home.component.tsx
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { KibanaPageTemplate } from '../../../../../../src/plugins/kibana_react/public';
+import { withSuspense } from '../../../../../../src/plugins/presentation_util/public';
+
+import { WorkpadCreate } from './workpad_create';
+import { LazyWorkpadTemplates } from './workpad_templates';
+import { LazyMyWorkpads } from './my_workpads';
+
+export type HomePageTab = 'workpads' | 'templates';
+
+export interface Props {
+ activeTab?: HomePageTab;
+}
+
+const WorkpadTemplates = withSuspense(LazyWorkpadTemplates);
+const MyWorkpads = withSuspense(LazyMyWorkpads);
+
+export const Home = ({ activeTab = 'workpads' }: Props) => {
+ const [tab, setTab] = useState(activeTab);
+
+ return (
+ ],
+ bottomBorder: true,
+ tabs: [
+ {
+ label: strings.getMyWorkpadsTabLabel(),
+ id: 'myWorkpads',
+ isSelected: tab === 'workpads',
+ onClick: () => setTab('workpads'),
+ },
+ {
+ label: strings.getWorkpadTemplatesTabLabel(),
+ id: 'workpadTemplates',
+ 'data-test-subj': 'workpadTemplates',
+ isSelected: tab === 'templates',
+ onClick: () => setTab('templates'),
+ },
+ ],
+ }}
+ >
+ {tab === 'workpads' ? : }
+
+ );
+};
+
+const strings = {
+ getMyWorkpadsTabLabel: () =>
+ i18n.translate('xpack.canvas.home.myWorkpadsTabLabel', {
+ defaultMessage: 'My workpads',
+ }),
+ getWorkpadTemplatesTabLabel: () =>
+ i18n.translate('xpack.canvas.home.workpadTemplatesTabLabel', {
+ defaultMessage: 'Templates',
+ description: 'The label for the tab that displays a list of designed workpad templates.',
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/home.stories.tsx b/x-pack/plugins/canvas/public/components/home/home.stories.tsx
new file mode 100644
index 0000000000000..186b916afa003
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/home.stories.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import {
+ reduxDecorator,
+ getAddonPanelParameters,
+ servicesContextDecorator,
+ getDisableStoryshotsParameter,
+} from '../../../storybook';
+
+import { Home } from './home.component';
+
+export default {
+ title: 'Home/Home Page',
+ argTypes: {},
+ decorators: [reduxDecorator()],
+ parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
+};
+
+export const NoContent = () => ;
+export const HasContent = () => ;
+
+NoContent.decorators = [servicesContextDecorator()];
+HasContent.decorators = [servicesContextDecorator({ findWorkpads: 5, findTemplates: true })];
diff --git a/x-pack/plugins/canvas/public/components/home/home.tsx b/x-pack/plugins/canvas/public/components/home/home.tsx
new file mode 100644
index 0000000000000..6b356ada8681e
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/home.tsx
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+import { getBaseBreadcrumb } from '../../lib/breadcrumbs';
+import { resetWorkpad } from '../../state/actions/workpad';
+import { Home as Component } from './home.component';
+import { usePlatformService } from '../../services';
+
+export const Home = () => {
+ const { setBreadcrumbs } = usePlatformService();
+ const [isMounted, setIsMounted] = useState(false);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ if (!isMounted) {
+ dispatch(resetWorkpad());
+ setIsMounted(true);
+ }
+ }, [dispatch, isMounted, setIsMounted]);
+
+ useEffect(() => {
+ setBreadcrumbs([getBaseBreadcrumb()]);
+ }, [setBreadcrumbs]);
+
+ return ;
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/index.ts b/x-pack/plugins/canvas/public/components/home/hooks/index.ts
new file mode 100644
index 0000000000000..91e52948a7ba6
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { useCloneWorkpad } from './use_clone_workpad';
+export { useCreateWorkpad } from './use_create_workpad';
+export { useDeleteWorkpads } from './use_delete_workpad';
+export { useDownloadWorkpad } from './use_download_workpad';
+export { useFindTemplates, useFindTemplatesOnMount } from './use_find_templates';
+export { useFindWorkpads, useFindWorkpadsOnMount } from './use_find_workpad';
+export { useImportWorkpad } from './use_upload_workpad';
+export { useCreateFromTemplate } from './use_create_from_template';
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
new file mode 100644
index 0000000000000..001a711a58a72
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+import { i18n } from '@kbn/i18n';
+
+import { useNotifyService, useWorkpadService } from '../../../services';
+import { getId } from '../../../lib/get_id';
+
+export const useCloneWorkpad = () => {
+ const workpadService = useWorkpadService();
+ const notifyService = useNotifyService();
+ const history = useHistory();
+
+ return useCallback(
+ async (workpadId: string) => {
+ try {
+ let workpad = await workpadService.get(workpadId);
+
+ workpad = {
+ ...workpad,
+ name: strings.getClonedWorkpadName(workpad.name),
+ id: getId('workpad'),
+ };
+
+ await workpadService.create(workpad);
+
+ history.push(`/workpad/${workpad.id}/page/1`);
+ } catch (err) {
+ notifyService.error(err, { title: errors.getCloneFailureErrorMessage() });
+ }
+ },
+ [notifyService, workpadService, history]
+ );
+};
+
+const strings = {
+ getClonedWorkpadName: (workpadName: string) =>
+ i18n.translate('xpack.canvas.useCloneWorkpad.clonedWorkpadName', {
+ defaultMessage: 'Copy of {workpadName}',
+ values: {
+ workpadName,
+ },
+ description:
+ 'This suffix is added to the end of the name of a cloned workpad to indicate that this ' +
+ 'new workpad is a copy of the original workpad. Example: "Copy of Sales Pitch"',
+ }),
+};
+
+const errors = {
+ getCloneFailureErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage', {
+ defaultMessage: `Couldn't clone workpad`,
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts
new file mode 100644
index 0000000000000..968f9398ba857
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+
+import { CanvasTemplate } from '../../../../types';
+import { useNotifyService, useWorkpadService } from '../../../services';
+
+export const useCreateFromTemplate = () => {
+ const workpadService = useWorkpadService();
+ const notifyService = useNotifyService();
+ const history = useHistory();
+
+ return useCallback(
+ async (template: CanvasTemplate) => {
+ try {
+ const result = await workpadService.createFromTemplate(template.id);
+ history.push(`/workpad/${result.id}/page/1`);
+ } catch (e) {
+ notifyService.error(e, {
+ title: `Couldn't create workpad from template`,
+ });
+ }
+ },
+ [workpadService, notifyService, history]
+ );
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts
new file mode 100644
index 0000000000000..eb87f4720deec
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+import { i18n } from '@kbn/i18n';
+
+// @ts-expect-error
+import { getDefaultWorkpad } from '../../../state/defaults';
+import { useNotifyService, useWorkpadService } from '../../../services';
+
+import type { CanvasWorkpad } from '../../../../types';
+
+export const useCreateWorkpad = () => {
+ const workpadService = useWorkpadService();
+ const notifyService = useNotifyService();
+ const history = useHistory();
+
+ return useCallback(
+ async (_workpad?: CanvasWorkpad | null) => {
+ const workpad = _workpad || (getDefaultWorkpad() as CanvasWorkpad);
+
+ try {
+ await workpadService.create(workpad);
+ history.push(`/workpad/${workpad.id}/page/1`);
+ } catch (err) {
+ notifyService.error(err, {
+ title: errors.getUploadFailureErrorMessage(),
+ });
+ }
+ return;
+ },
+ [notifyService, history, workpadService]
+ );
+};
+
+const errors = {
+ getUploadFailureErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage', {
+ defaultMessage: `Couldn't upload workpad`,
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
new file mode 100644
index 0000000000000..722ddae7411c9
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { useNotifyService, useWorkpadService } from '../../../services';
+
+export const useDeleteWorkpads = () => {
+ const workpadService = useWorkpadService();
+ const notifyService = useNotifyService();
+
+ return useCallback(
+ async (workpadIds: string[]) => {
+ const removedWorkpads = workpadIds.map(async (id) => {
+ try {
+ await workpadService.remove(id);
+ return { id, err: null };
+ } catch (err) {
+ return { id, err };
+ }
+ });
+
+ return Promise.all(removedWorkpads).then((results) => {
+ const [passes, errored] = results.reduce<[string[], string[]]>(
+ ([passesArr, errorsArr], result) => {
+ if (result.err) {
+ errorsArr.push(result.id);
+ } else {
+ passesArr.push(result.id);
+ }
+
+ return [passesArr, errorsArr];
+ },
+ [[], []]
+ );
+
+ const removedIds = workpadIds.filter((id) => passes.includes(id));
+
+ if (errored.length > 0) {
+ notifyService.error(errors.getDeleteFailureErrorMessage());
+ }
+
+ return {
+ removedIds,
+ errored,
+ };
+ });
+ },
+ [workpadService, notifyService]
+ );
+};
+
+const errors = {
+ getDeleteFailureErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage', {
+ defaultMessage: `Couldn't delete all workpads`,
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_download_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_download_workpad.ts
new file mode 100644
index 0000000000000..b875e08c2a230
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_download_workpad.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { downloadWorkpad as downloadWorkpadFn } from '../../../lib/download_workpad';
+
+export const useDownloadWorkpad = () =>
+ useCallback((workpadId: string) => downloadWorkpadFn(workpadId), []);
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
new file mode 100644
index 0000000000000..13ee289fe9867
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useState, useCallback } from 'react';
+import useMount from 'react-use/lib/useMount';
+
+import { useWorkpadService } from '../../../services';
+import { TemplateFindResponse } from '../../../services/workpad';
+
+const emptyResponse = { templates: [] };
+
+export const useFindTemplates = () => {
+ const workpadService = useWorkpadService();
+ return useCallback(async () => await workpadService.findTemplates(), [workpadService]);
+};
+
+export const useFindTemplatesOnMount = (): [boolean, TemplateFindResponse] => {
+ const [isMounted, setIsMounted] = useState(false);
+ const findTemplates = useFindTemplates();
+ const [templateResponse, setTemplateResponse] = useState(emptyResponse);
+
+ const fetchTemplates = useCallback(async () => {
+ const foundTemplates = await findTemplates();
+ setTemplateResponse(foundTemplates || emptyResponse);
+ setIsMounted(true);
+ }, [findTemplates]);
+
+ useMount(() => {
+ fetchTemplates();
+ return () => setIsMounted(false);
+ });
+
+ return [isMounted, templateResponse];
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
new file mode 100644
index 0000000000000..3f8b0e6f630f5
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useState, useCallback } from 'react';
+import useMount from 'react-use/lib/useMount';
+import { i18n } from '@kbn/i18n';
+
+import { WorkpadFindResponse } from '../../../services/workpad';
+
+import { useNotifyService, useWorkpadService } from '../../../services';
+const emptyResponse = { total: 0, workpads: [] };
+
+export const useFindWorkpads = () => {
+ const workpadService = useWorkpadService();
+ const notifyService = useNotifyService();
+
+ return useCallback(
+ async (text = '') => {
+ try {
+ return await workpadService.find(text);
+ } catch (err) {
+ notifyService.error(err, { title: errors.getFindFailureErrorMessage() });
+ }
+ },
+ [notifyService, workpadService]
+ );
+};
+
+export const useFindWorkpadsOnMount = (): [boolean, WorkpadFindResponse] => {
+ const [isMounted, setIsMounted] = useState(false);
+ const findWorkpads = useFindWorkpads();
+ const [workpadResponse, setWorkpadResponse] = useState(emptyResponse);
+
+ const fetchWorkpads = useCallback(async () => {
+ const foundWorkpads = await findWorkpads();
+ setWorkpadResponse(foundWorkpads || emptyResponse);
+ setIsMounted(true);
+ }, [findWorkpads]);
+
+ useMount(() => {
+ fetchWorkpads();
+ return () => setIsMounted(false);
+ });
+
+ return [isMounted, workpadResponse];
+};
+
+const errors = {
+ getFindFailureErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useFindWorkpads.findFailureErrorMessage', {
+ defaultMessage: `Couldn't find workpad`,
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts
new file mode 100644
index 0000000000000..7934a469bb7a2
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback } from 'react';
+import { get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+
+import { CANVAS, JSON as JSONString } from '../../../../i18n/constants';
+import { useNotifyService } from '../../../services';
+import { getId } from '../../../lib/get_id';
+import type { CanvasWorkpad } from '../../../../types';
+
+export const useImportWorkpad = () => {
+ const notifyService = useNotifyService();
+
+ return useCallback(
+ (file?: File, onComplete: (workpad?: CanvasWorkpad) => void = () => {}) => {
+ if (!file) {
+ onComplete();
+ return;
+ }
+
+ if (get(file, 'type') !== 'application/json') {
+ notifyService.warning(errors.getAcceptJSONOnlyErrorMessage(), {
+ title: file.name
+ ? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
+ : errors.getFileUploadFailureWithoutFileNameErrorMessage(),
+ });
+ onComplete();
+ }
+
+ // TODO: Clean up this file, this loading stuff can, and should be, abstracted
+ const reader = new FileReader();
+
+ // handle reading the uploaded file
+ reader.onload = () => {
+ try {
+ const workpad = JSON.parse(reader.result as string); // Type-casting because we catch below.
+ workpad.id = getId('workpad');
+
+ // sanity check for workpad object
+ if (!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets) {
+ onComplete();
+ throw new Error(errors.getMissingPropertiesErrorMessage());
+ }
+
+ onComplete(workpad);
+ } catch (e) {
+ notifyService.error(e, {
+ title: file.name
+ ? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
+ : errors.getFileUploadFailureWithoutFileNameErrorMessage(),
+ });
+ onComplete();
+ }
+ };
+
+ // read the uploaded file
+ reader.readAsText(file);
+ },
+ [notifyService]
+ );
+};
+
+const errors = {
+ getFileUploadFailureWithoutFileNameErrorMessage: () =>
+ i18n.translate(
+ 'xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage',
+ {
+ defaultMessage: `Couldn't upload file`,
+ }
+ ),
+ getFileUploadFailureWithFileNameErrorMessage: (fileName: string) =>
+ i18n.translate('xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage', {
+ defaultMessage: `Couldn't upload '{fileName}'`,
+ values: {
+ fileName,
+ },
+ }),
+ getMissingPropertiesErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage', {
+ defaultMessage:
+ 'Some properties required for a {CANVAS} workpad are missing. Edit your {JSON} file to provide the correct property values, and try again.',
+ values: {
+ CANVAS,
+ JSON: JSONString,
+ },
+ }),
+ getAcceptJSONOnlyErrorMessage: () =>
+ i18n.translate('xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage', {
+ defaultMessage: 'Only {JSON} files are accepted',
+ values: {
+ JSON: JSONString,
+ },
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/workpad_manager/index.js b/x-pack/plugins/canvas/public/components/home/index.ts
similarity index 83%
rename from x-pack/plugins/canvas/public/components/workpad_manager/index.js
rename to x-pack/plugins/canvas/public/components/home/index.ts
index e1f5855e762af..aeb62c3a8de78 100644
--- a/x-pack/plugins/canvas/public/components/workpad_manager/index.js
+++ b/x-pack/plugins/canvas/public/components/home/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export { WorkpadManager } from './workpad_manager';
+export { Home } from './home';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.stories.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.stories.tsx
new file mode 100644
index 0000000000000..aef1b0625b585
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.stories.tsx
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { HomeEmptyPrompt } from './empty_prompt';
+import { getDisableStoryshotsParameter } from '../../../../storybook';
+
+export default {
+ title: 'Home/Empty Prompt',
+ argTypes: {},
+ parameters: { ...getDisableStoryshotsParameter() },
+};
+
+export const EmptyPrompt = () => ;
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.tsx
new file mode 100644
index 0000000000000..797f50ac112d0
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/empty_prompt.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+import { CANVAS, JSON } from '../../../../i18n/constants';
+
+export const HomeEmptyPrompt = () => (
+
+
+
+ {strings.getEmptyPromptTitle()}}
+ titleSize="m"
+ body={
+
+ {strings.getEmptyPromptGettingStartedDescription()}
+
+ {strings.getEmptyPromptNewUserDescription()}{' '}
+
+ {strings.getSampleDataLinkLabel()}
+
+ .
+
+
+ }
+ />
+
+
+
+);
+
+const strings = {
+ getEmptyPromptGettingStartedDescription: () =>
+ i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription', {
+ defaultMessage:
+ 'Create a new workpad, start from a template, or import a workpad {JSON} file by dropping it here.',
+ values: {
+ JSON,
+ },
+ }),
+ getEmptyPromptNewUserDescription: () =>
+ i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription', {
+ defaultMessage: 'New to {CANVAS}?',
+ values: {
+ CANVAS,
+ },
+ }),
+ getEmptyPromptTitle: () =>
+ i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptTitle', {
+ defaultMessage: 'Add your first workpad',
+ }),
+ getSampleDataLinkLabel: () =>
+ i18n.translate('xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel', {
+ defaultMessage: 'Add your first workpad',
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/index.ts b/x-pack/plugins/canvas/public/components/home/my_workpads/index.ts
new file mode 100644
index 0000000000000..79b1519df90fe
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+export const LazyMyWorkpads = React.lazy(() => import('./my_workpads'));
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx
new file mode 100644
index 0000000000000..28edfea7c36ca
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+export const Loading = () => (
+
+
+
+
+
+);
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
new file mode 100644
index 0000000000000..d9e3f0e4e2c99
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+import { FoundWorkpad } from '../../../services/workpad';
+import { UploadDropzone } from './upload_dropzone';
+import { HomeEmptyPrompt } from './empty_prompt';
+import { WorkpadTable } from './workpad_table';
+
+export interface Props {
+ workpads: FoundWorkpad[];
+}
+
+export const MyWorkpads = ({ workpads }: Props) => {
+ if (workpads.length === 0) {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
new file mode 100644
index 0000000000000..0d5d6ca16f614
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { EuiPanel } from '@elastic/eui';
+
+import {
+ reduxDecorator,
+ getAddonPanelParameters,
+ servicesContextDecorator,
+ getDisableStoryshotsParameter,
+} from '../../../../storybook';
+import { getSomeWorkpads } from '../../../services/stubs/workpad';
+
+import { MyWorkpads, WorkpadsContext } from './my_workpads';
+import { MyWorkpads as MyWorkpadsComponent } from './my_workpads.component';
+
+export default {
+ title: 'Home/My Workpads',
+ argTypes: {},
+ decorators: [reduxDecorator()],
+ parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
+};
+
+export const NoWorkpads = () => {
+ return ;
+};
+
+export const HasWorkpads = () => {
+ return (
+
+
+
+ );
+};
+
+NoWorkpads.decorators = [servicesContextDecorator()];
+HasWorkpads.decorators = [servicesContextDecorator({ findWorkpads: 5 })];
+
+export const Component = ({ workpadCount }: { workpadCount: number }) => {
+ const [workpads, setWorkpads] = useState(getSomeWorkpads(workpadCount));
+
+ return (
+
+
+
+
+
+ );
+};
+
+Component.args = { workpadCount: 5 };
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
new file mode 100644
index 0000000000000..4242e2e9d130f
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, useEffect, createContext, Dispatch, SetStateAction } from 'react';
+import { useFindWorkpadsOnMount } from './../hooks';
+import { FoundWorkpad } from '../../../services/workpad';
+import { Loading } from './loading';
+import { MyWorkpads as Component } from './my_workpads.component';
+
+interface Context {
+ workpads: FoundWorkpad[];
+ setWorkpads: Dispatch>;
+}
+
+export const WorkpadsContext = createContext(null);
+
+export const MyWorkpads = () => {
+ const [isMounted, workpadResponse] = useFindWorkpadsOnMount();
+ const [workpads, setWorkpads] = useState(workpadResponse.workpads);
+
+ useEffect(() => {
+ setWorkpads(workpadResponse.workpads);
+ }, [workpadResponse]);
+
+ if (!isMounted) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+};
+
+// required for dynamic import using React.lazy()
+// eslint-disable-next-line import/no-default-export
+export default MyWorkpads;
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.component.tsx
new file mode 100644
index 0000000000000..603f4679a9e95
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.component.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { FC } from 'react';
+// @ts-expect-error untyped library
+import Dropzone from 'react-dropzone';
+
+import './upload_dropzone.scss';
+
+export interface Props {
+ disabled?: boolean;
+ onDrop?: (files: FileList) => void;
+}
+
+export const UploadDropzone: FC = ({ onDrop = () => {}, disabled, children }) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.scss b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.scss
new file mode 100644
index 0000000000000..e4ee284c72dee
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.scss
@@ -0,0 +1,8 @@
+.canvasWorkpad__dropzone {
+ border: 2px dashed transparent;
+}
+
+.canvasWorkpad__dropzone--active {
+ background-color: $euiColorLightestShade;
+ border-color: $euiColorLightShade;
+}
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.tsx
new file mode 100644
index 0000000000000..8ee0ae108392e
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/upload_dropzone.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { FC, useState } from 'react';
+// @ts-expect-error untyped library
+import Dropzone from 'react-dropzone';
+
+import { useNotifyService } from '../../../services';
+import { ErrorStrings } from '../../../../i18n';
+import { useImportWorkpad, useCreateWorkpad } from '../hooks';
+import { CanvasWorkpad } from '../../../../types';
+
+import { UploadDropzone as Component } from './upload_dropzone.component';
+
+const { WorkpadDropzone: errors } = ErrorStrings;
+
+export const UploadDropzone: FC = ({ children }) => {
+ const notify = useNotifyService();
+ const uploadWorkpad = useImportWorkpad();
+ const createWorkpad = useCreateWorkpad();
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ const onComplete = async (workpad?: CanvasWorkpad) => {
+ if (!workpad) {
+ setIsDisabled(false);
+ return;
+ }
+
+ await createWorkpad(workpad);
+ };
+
+ const onDrop = (files: FileList) => {
+ if (!files) {
+ return;
+ }
+
+ if (files.length > 1) {
+ notify.warning(errors.getTooManyFilesErrorMessage());
+ return;
+ }
+
+ setIsDisabled(true);
+ uploadWorkpad(files[0], onComplete);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.component.tsx
new file mode 100644
index 0000000000000..28e2aa0449d46
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.component.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiFilePicker, EuiFilePickerProps } from '@elastic/eui';
+
+import { JSON } from '../../../../i18n/constants';
+export interface Props {
+ canUserWrite: boolean;
+ onImportWorkpad?: EuiFilePickerProps['onChange'];
+ uniqueKey?: string | number;
+}
+
+export const WorkpadImport = ({ uniqueKey, canUserWrite, onImportWorkpad = () => {} }: Props) => (
+
+);
+
+const strings = {
+ getFilePickerPlaceholder: () =>
+ i18n.translate('xpack.canvas.workpadImport.filePickerPlaceholder', {
+ defaultMessage: 'Import workpad {JSON} file',
+ values: {
+ JSON,
+ },
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.tsx
new file mode 100644
index 0000000000000..0f1ba621e14d7
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_import.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+
+import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
+import type { State } from '../../../../types';
+
+import { useImportWorkpad } from '../hooks';
+import { WorkpadImport as Component, Props as ComponentProps } from './workpad_import.component';
+
+type Props = Omit;
+
+export const WorkpadImport = (props: Props) => {
+ const importWorkpad = useImportWorkpad();
+ const [uniqueKey, setUniqueKey] = useState(Date.now());
+
+ const { canUserWrite } = useSelector((state: State) => ({
+ canUserWrite: canUserWriteSelector(state),
+ }));
+
+ const onImportWorkpad: ComponentProps['onImportWorkpad'] = (files) => {
+ if (files) {
+ importWorkpad(files[0]);
+ }
+ setUniqueKey(Date.now());
+ };
+
+ return ;
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
new file mode 100644
index 0000000000000..5301a88844369
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
@@ -0,0 +1,203 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiInMemoryTable,
+ EuiInMemoryTableProps,
+ EuiTableActionsColumnType,
+ EuiBasicTableColumn,
+ EuiToolTip,
+ EuiButtonIcon,
+ EuiTableSelectionType,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '@elastic/eui';
+import moment from 'moment';
+
+import { RoutingLink } from '../../routing';
+import { FoundWorkpad } from '../../../services/workpad';
+import { WorkpadTableTools } from './workpad_table_tools';
+import { WorkpadImport } from './workpad_import';
+
+export interface Props {
+ workpads: FoundWorkpad[];
+ canUserWrite: boolean;
+ dateFormat: string;
+ onExportWorkpad: (ids: string) => void;
+ onCloneWorkpad: (id: string) => void;
+}
+
+const getDisplayName = (name: string, workpadId: string, loadedWorkpadId?: string) => {
+ const workpadName = name.length ? {name} : {workpadId} ;
+ return workpadId === loadedWorkpadId ? {workpadName} : workpadName;
+};
+
+export const WorkpadTable = ({
+ workpads,
+ canUserWrite,
+ dateFormat,
+ onExportWorkpad: onExport,
+ onCloneWorkpad,
+}: Props) => {
+ const [selectedIds, setSelectedIds] = useState([]);
+ const formatDate = (date: string) => date && moment(date).format(dateFormat);
+
+ const selection: EuiTableSelectionType = {
+ onSelectionChange: (selectedWorkpads) => {
+ setSelectedIds(selectedWorkpads.map((workpad) => workpad.id).filter((id) => !!id));
+ },
+ };
+
+ const actions: EuiTableActionsColumnType['actions'] = [
+ {
+ render: (workpad: FoundWorkpad) => (
+
+
+
+ onExport(workpad.id)}
+ aria-label={strings.getExportToolTip()}
+ />
+
+
+
+
+ onCloneWorkpad(workpad.id)}
+ aria-label={strings.getCloneToolTip()}
+ disabled={!canUserWrite}
+ />
+
+
+
+ ),
+ },
+ ];
+
+ const search: EuiInMemoryTableProps['search'] = {
+ toolsLeft:
+ selectedIds.length > 0 ? : undefined,
+ toolsRight: ,
+ box: {
+ schema: true,
+ incremental: true,
+ placeholder: strings.getWorkpadSearchPlaceholder(),
+ 'data-test-subj': 'tableListSearchBox',
+ },
+ };
+
+ const columns: Array> = [
+ {
+ field: 'name',
+ name: strings.getTableNameColumnTitle(),
+ sortable: true,
+ dataType: 'string',
+ render: (name, workpad) => (
+
+ {getDisplayName(name, workpad.id)}
+
+ ),
+ },
+ {
+ field: '@created',
+ name: strings.getTableCreatedColumnTitle(),
+ sortable: true,
+ dataType: 'date',
+ width: '20%',
+ render: (date: string) => formatDate(date),
+ },
+ {
+ field: '@timestamp',
+ name: strings.getTableUpdatedColumnTitle(),
+ sortable: true,
+ dataType: 'date',
+ width: '20%',
+ render: (date: string) => formatDate(date),
+ },
+ { name: strings.getTableActionsColumnTitle(), actions, width: '100px' },
+ ];
+
+ return (
+
+ );
+};
+
+const strings = {
+ getCloneToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTable.cloneTooltip', {
+ defaultMessage: 'Clone workpad',
+ }),
+ getExportToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTable.exportTooltip', {
+ defaultMessage: 'Export workpad',
+ }),
+ getLoadWorkpadArialLabel: (workpadName: string) =>
+ i18n.translate('xpack.canvas.workpadTable.loadWorkpadArialLabel', {
+ defaultMessage: `Load workpad '{workpadName}'`,
+ values: {
+ workpadName,
+ },
+ }),
+ getNoPermissionToCloneToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTable.noPermissionToCloneToolTip', {
+ defaultMessage: `You don't have permission to clone workpads`,
+ }),
+ getNoWorkpadsFoundMessage: () =>
+ i18n.translate('xpack.canvas.workpadTable.noWorkpadsFoundMessage', {
+ defaultMessage: 'No workpads matched your search.',
+ }),
+ getWorkpadSearchPlaceholder: () =>
+ i18n.translate('xpack.canvas.workpadTable.searchPlaceholder', {
+ defaultMessage: 'Find workpad',
+ }),
+ getTableCreatedColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTable.table.createdColumnTitle', {
+ defaultMessage: 'Created',
+ description: 'This column in the table contains the date/time the workpad was created.',
+ }),
+ getTableNameColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTable.table.nameColumnTitle', {
+ defaultMessage: 'Workpad name',
+ }),
+ getTableUpdatedColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTable.table.updatedColumnTitle', {
+ defaultMessage: 'Updated',
+ description: 'This column in the table contains the date/time the workpad was last updated.',
+ }),
+ getTableActionsColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTable.table.actionsColumnTitle', {
+ defaultMessage: 'Actions',
+ description: 'This column in the table contains the actions that can be taken on a workpad.',
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
new file mode 100644
index 0000000000000..501a0a76a8589
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
@@ -0,0 +1,83 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { EuiPanel } from '@elastic/eui';
+
+import { action } from '@storybook/addon-actions';
+import {
+ reduxDecorator,
+ getAddonPanelParameters,
+ getDisableStoryshotsParameter,
+} from '../../../../storybook';
+import { getSomeWorkpads } from '../../../services/stubs/workpad';
+
+import { WorkpadTable } from './workpad_table';
+import { WorkpadTable as WorkpadTableComponent } from './workpad_table.component';
+import { WorkpadsContext } from './my_workpads';
+
+export default {
+ title: 'Home/Workpad Table',
+ argTypes: {},
+ decorators: [reduxDecorator()],
+ parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
+};
+
+export const NoWorkpads = () => {
+ const [workpads, setWorkpads] = useState(getSomeWorkpads(0));
+
+ return (
+
+
+
+
+
+ );
+};
+
+export const HasWorkpads = () => {
+ const [workpads, setWorkpads] = useState(getSomeWorkpads(5));
+
+ return (
+
+
+
+
+
+ );
+};
+
+export const Component = ({
+ workpadCount,
+ canUserWrite,
+ dateFormat,
+}: {
+ workpadCount: number;
+ canUserWrite: boolean;
+ dateFormat: string;
+}) => {
+ const [workpads, setWorkpads] = useState(getSomeWorkpads(workpadCount));
+
+ useEffect(() => {
+ setWorkpads(getSomeWorkpads(workpadCount));
+ }, [workpadCount]);
+
+ return (
+
+
+
+
+
+ );
+};
+
+Component.args = { workpadCount: 5, canUserWrite: true, dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS' };
+Component.argTypes = {};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
new file mode 100644
index 0000000000000..e5d83039a87eb
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useContext } from 'react';
+import { useSelector } from 'react-redux';
+
+import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
+import type { State } from '../../../../types';
+import { usePlatformService } from '../../../services';
+import { useCloneWorkpad, useDownloadWorkpad } from '../hooks';
+
+import { WorkpadTable as Component } from './workpad_table.component';
+import { WorkpadsContext } from './my_workpads';
+
+export const WorkpadTable = () => {
+ const platformService = usePlatformService();
+ const onCloneWorkpad = useCloneWorkpad();
+ const onExportWorkpad = useDownloadWorkpad();
+ const context = useContext(WorkpadsContext);
+
+ const { canUserWrite } = useSelector((state: State) => ({
+ canUserWrite: canUserWriteSelector(state),
+ }));
+
+ if (!context) {
+ return null;
+ }
+
+ const { workpads } = context;
+
+ const dateFormat = platformService.getUISetting('dateFormat');
+
+ return ;
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
new file mode 100644
index 0000000000000..ae6ff9c3cc910
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiButton, EuiToolTip, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
+
+import { ConfirmModal } from '../../confirm_modal';
+import { FoundWorkpad } from '../../../services/workpad';
+
+export interface Props {
+ workpads: FoundWorkpad[];
+ canUserWrite: boolean;
+ selectedWorkpadIds: string[];
+ onDeleteWorkpads: (ids: string[]) => void;
+ onExportWorkpads: (ids: string[]) => void;
+}
+
+export const WorkpadTableTools = ({
+ workpads,
+ canUserWrite,
+ selectedWorkpadIds,
+ onDeleteWorkpads,
+ onExportWorkpads,
+}: Props) => {
+ const [isDeletePending, setIsDeletePending] = useState(false);
+
+ const openRemoveConfirm = () => setIsDeletePending(true);
+ const closeRemoveConfirm = () => setIsDeletePending(false);
+
+ let deleteButton = (
+
+ {strings.getDeleteButtonLabel(selectedWorkpadIds.length)}
+
+ );
+
+ const downloadButton = (
+ onExportWorkpads(selectedWorkpadIds)}
+ iconType="exportAction"
+ aria-label={strings.getExportButtonAriaLabel(selectedWorkpadIds.length)}
+ >
+ {strings.getExportButtonLabel(selectedWorkpadIds.length)}
+
+ );
+
+ if (!canUserWrite) {
+ deleteButton = (
+ {deleteButton}
+ );
+ }
+
+ const modalTitle =
+ selectedWorkpadIds.length === 1
+ ? strings.getDeleteSingleWorkpadModalTitle(
+ workpads.find((workpad) => workpad.id === selectedWorkpadIds[0])?.name || ''
+ )
+ : strings.getDeleteMultipleWorkpadModalTitle(selectedWorkpadIds.length + '');
+
+ const confirmModal = (
+ {
+ onDeleteWorkpads(selectedWorkpadIds);
+ closeRemoveConfirm();
+ }}
+ onCancel={closeRemoveConfirm}
+ />
+ );
+
+ return (
+
+
+ {downloadButton}
+ {deleteButton}
+
+ {confirmModal}
+
+ );
+};
+
+const strings = {
+ getDeleteButtonAriaLabel: (numberOfWorkpads: number) =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteButtonAriaLabel', {
+ defaultMessage: 'Delete {numberOfWorkpads} workpads',
+ values: {
+ numberOfWorkpads,
+ },
+ }),
+ getDeleteButtonLabel: (numberOfWorkpads: number) =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteButtonLabel', {
+ defaultMessage: 'Delete ({numberOfWorkpads})',
+ values: {
+ numberOfWorkpads,
+ },
+ }),
+ getDeleteModalConfirmButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel', {
+ defaultMessage: 'Delete',
+ }),
+ getDeleteModalDescription: () =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteModalDescription', {
+ defaultMessage: `You can't recover deleted workpads.`,
+ }),
+ getDeleteMultipleWorkpadModalTitle: (numberOfWorkpads: string) =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle', {
+ defaultMessage: 'Delete {numberOfWorkpads} workpads?',
+ values: {
+ numberOfWorkpads,
+ },
+ }),
+ getDeleteSingleWorkpadModalTitle: (workpadName: string) =>
+ i18n.translate('xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle', {
+ defaultMessage: `Delete workpad '{workpadName}'?`,
+ values: {
+ workpadName,
+ },
+ }),
+ getExportButtonAriaLabel: (numberOfWorkpads: number) =>
+ i18n.translate('xpack.canvas.workpadTableTools.exportButtonAriaLabel', {
+ defaultMessage: 'Export {numberOfWorkpads} workpads',
+ values: {
+ numberOfWorkpads,
+ },
+ }),
+ getExportButtonLabel: (numberOfWorkpads: number) =>
+ i18n.translate('xpack.canvas.workpadTableTools.exportButtonLabel', {
+ defaultMessage: 'Export ({numberOfWorkpads})',
+ values: {
+ numberOfWorkpads,
+ },
+ }),
+ getNoPermissionToCreateToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTableTools.noPermissionToCreateToolTip', {
+ defaultMessage: `You don't have permission to create workpads`,
+ }),
+ getNoPermissionToDeleteToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip', {
+ defaultMessage: `You don't have permission to delete workpads`,
+ }),
+ getNoPermissionToUploadToolTip: () =>
+ i18n.translate('xpack.canvas.workpadTableTools.noPermissionToUploadToolTip', {
+ defaultMessage: `You don't have permission to upload workpads`,
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.tsx
new file mode 100644
index 0000000000000..62d84adfc2649
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useContext } from 'react';
+import { useSelector } from 'react-redux';
+
+import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
+import type { State } from '../../../../types';
+import { useDeleteWorkpads, useDownloadWorkpad } from '../hooks';
+
+import {
+ WorkpadTableTools as Component,
+ Props as ComponentProps,
+} from './workpad_table_tools.component';
+import { WorkpadsContext } from './my_workpads';
+
+export type Props = Pick;
+
+export const WorkpadTableTools = ({ selectedWorkpadIds }: Props) => {
+ const deleteWorkpads = useDeleteWorkpads();
+ const downloadWorkpad = useDownloadWorkpad();
+ const context = useContext(WorkpadsContext);
+
+ const { canUserWrite } = useSelector((state: State) => ({
+ canUserWrite: canUserWriteSelector(state),
+ }));
+
+ if (context === null || selectedWorkpadIds.length <= 0) {
+ return null;
+ }
+
+ const { workpads, setWorkpads } = context;
+
+ const onExport = () => selectedWorkpadIds.map((id) => downloadWorkpad(id));
+ const onDelete = async () => {
+ const { removedIds } = await deleteWorkpads(selectedWorkpadIds);
+ setWorkpads(workpads.filter((workpad) => !removedIds.includes(workpad.id)));
+ };
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_create.component.tsx b/x-pack/plugins/canvas/public/components/home/workpad_create.component.tsx
new file mode 100644
index 0000000000000..18bdb97683194
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_create.component.tsx
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiButton } from '@elastic/eui';
+import { EuiButtonPropsForButton } from '@elastic/eui/src/components/button/button';
+
+export interface Props
+ extends Omit {
+ canUserWrite: boolean;
+}
+
+export const WorkpadCreate = ({ canUserWrite, disabled, ...rest }: Props) => {
+ return (
+
+ {strings.getWorkpadCreateButtonLabel()}
+
+ );
+};
+
+const strings = {
+ getWorkpadCreateButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadCreate.createButtonLabel', {
+ defaultMessage: 'Create workpad',
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_create.tsx b/x-pack/plugins/canvas/public/components/home/workpad_create.tsx
new file mode 100644
index 0000000000000..adb73a6bb8896
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_create.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+import { canUserWrite as canUserWriteSelector } from '../../state/selectors/app';
+import type { State } from '../../../types';
+
+import { useCreateWorkpad } from './hooks';
+import { WorkpadCreate as Component, Props as ComponentProps } from './workpad_create.component';
+
+type Props = Omit;
+
+export const WorkpadCreate = (props: Props) => {
+ const createWorkpad = useCreateWorkpad();
+
+ const { canUserWrite } = useSelector((state: State) => ({
+ canUserWrite: canUserWriteSelector(state),
+ }));
+
+ const onClick: ComponentProps['onClick'] = async () => {
+ await createWorkpad();
+ };
+
+ return ;
+};
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/index.ts b/x-pack/plugins/canvas/public/components/home/workpad_templates/index.ts
new file mode 100644
index 0000000000000..4c45dbff38377
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+export const LazyWorkpadTemplates = React.lazy(() => import('./workpad_templates'));
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.component.tsx b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.component.tsx
new file mode 100644
index 0000000000000..d974c70b05cf2
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.component.tsx
@@ -0,0 +1,157 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { uniq } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiInMemoryTable,
+ EuiBasicTableColumn,
+ EuiButtonEmpty,
+ EuiSearchBarProps,
+ SearchFilterConfig,
+} from '@elastic/eui';
+
+import { CanvasTemplate } from '../../../../types';
+import { tagsRegistry } from '../../../lib/tags_registry';
+import { TagList } from '../../tag_list';
+
+export interface Props {
+ templates: CanvasTemplate[];
+ onCreateWorkpad: (template: CanvasTemplate) => void;
+}
+
+export const WorkpadTemplates = ({ templates, onCreateWorkpad }: Props) => {
+ const columns: Array> = [
+ {
+ field: 'name',
+ name: strings.getTableNameColumnTitle(),
+ sortable: true,
+ width: '30%',
+ dataType: 'string',
+ render: (name: string, template) => {
+ const templateName = name.length ? name : 'Unnamed Template';
+
+ return (
+ onCreateWorkpad(template)}
+ aria-label={strings.getCloneTemplateLinkAriaLabel(templateName)}
+ type="button"
+ >
+ {templateName}
+
+ );
+ },
+ },
+ {
+ field: 'help',
+ name: strings.getTableDescriptionColumnTitle(),
+ sortable: false,
+ dataType: 'string',
+ width: '30%',
+ },
+ {
+ field: 'tags',
+ name: strings.getTableTagsColumnTitle(),
+ sortable: false,
+ dataType: 'string',
+ width: '30%',
+ render: (tags: string[]) => ,
+ },
+ ];
+
+ let uniqueTagNames: string[] = [];
+
+ templates.forEach((template) => {
+ const { tags } = template;
+ tags.forEach((tag) => uniqueTagNames.push(tag));
+ uniqueTagNames = uniq(uniqueTagNames);
+ });
+
+ const uniqueTags = uniqueTagNames.map(
+ (name) =>
+ tagsRegistry.get(name) || {
+ color: undefined,
+ name,
+ }
+ );
+
+ const filters: SearchFilterConfig[] = [
+ {
+ type: 'field_value_selection',
+ field: 'tags',
+ name: 'Tags',
+ multiSelect: true,
+ options: uniqueTags.map((tag) => ({
+ value: tag.name,
+ name: tag.name,
+ view: ,
+ })),
+ },
+ ];
+
+ const search: EuiSearchBarProps = {
+ box: {
+ incremental: true,
+ schema: true,
+ },
+ filters,
+ };
+
+ return (
+
+ );
+};
+
+const strings = {
+ getCloneTemplateLinkAriaLabel: (templateName: string) =>
+ i18n.translate('xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel', {
+ defaultMessage: `Clone workpad template '{templateName}'`,
+ values: {
+ templateName,
+ },
+ }),
+ getTableDescriptionColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTemplates.table.descriptionColumnTitle', {
+ defaultMessage: 'Description',
+ }),
+ getTableNameColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTemplates.table.nameColumnTitle', {
+ defaultMessage: 'Template name',
+ }),
+ getTableTagsColumnTitle: () =>
+ i18n.translate('xpack.canvas.workpadTemplates.table.tagsColumnTitle', {
+ defaultMessage: 'Tags',
+ description:
+ 'This column contains relevant tags that indicate what type of template ' +
+ 'is displayed. For example: "report", "presentation", etc.',
+ }),
+ getTemplateSearchPlaceholder: () =>
+ i18n.translate('xpack.canvas.workpadTemplates.searchPlaceholder', {
+ defaultMessage: 'Find template',
+ }),
+ getCreatingTemplateLabel: (templateName: string) =>
+ i18n.translate('xpack.canvas.workpadTemplates.creatingTemplateLabel', {
+ defaultMessage: `Creating from template '{templateName}'`,
+ values: {
+ templateName,
+ },
+ }),
+};
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
new file mode 100644
index 0000000000000..cb2b872ea15f9
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiPanel } from '@elastic/eui';
+import { action } from '@storybook/addon-actions';
+import React from 'react';
+
+import {
+ reduxDecorator,
+ getAddonPanelParameters,
+ servicesContextDecorator,
+ getDisableStoryshotsParameter,
+} from '../../../../storybook';
+import { getSomeTemplates } from '../../../services/stubs/workpad';
+
+import { WorkpadTemplates } from './workpad_templates';
+import { WorkpadTemplates as WorkpadTemplatesComponent } from './workpad_templates.component';
+
+export default {
+ title: 'Home/Workpad Templates',
+ argTypes: {},
+ decorators: [reduxDecorator()],
+ parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
+};
+
+export const NoTemplates = () => {
+ return (
+
+
+
+ );
+};
+
+export const HasTemplates = () => {
+ return (
+
+
+
+ );
+};
+
+NoTemplates.decorators = [servicesContextDecorator()];
+HasTemplates.decorators = [servicesContextDecorator({ findTemplates: true })];
+
+export const Component = ({ hasTemplates }: { hasTemplates: boolean }) => {
+ return (
+
+
+
+ );
+};
+
+Component.args = {
+ hasTemplates: true,
+};
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
new file mode 100644
index 0000000000000..352285e66424b
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+
+import { useCreateFromTemplate, useFindTemplatesOnMount } from '../hooks';
+
+import { WorkpadTemplates as Component } from './workpad_templates.component';
+
+export const WorkpadTemplates = () => {
+ const [isMounted, templateResponse] = useFindTemplatesOnMount();
+ const onCreateWorkpad = useCreateFromTemplate();
+
+ if (!isMounted) {
+ return (
+
+
+
+
+
+ );
+ }
+ const { templates } = templateResponse;
+
+ return ;
+};
+
+// required for dynamic import using React.lazy()
+// eslint-disable-next-line import/no-default-export
+export default WorkpadTemplates;
diff --git a/x-pack/plugins/canvas/public/components/home_app/home_app.component.tsx b/x-pack/plugins/canvas/public/components/home_app/home_app.component.tsx
index 712b06cb39299..2e3e826cc32b5 100644
--- a/x-pack/plugins/canvas/public/components/home_app/home_app.component.tsx
+++ b/x-pack/plugins/canvas/public/components/home_app/home_app.component.tsx
@@ -6,9 +6,7 @@
*/
import React, { FC } from 'react';
-import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
-// @ts-expect-error untyped local
-import { WorkpadManager } from '../workpad_manager';
+import { Home } from '../home';
// @ts-expect-error untyped local
import { setDocTitle } from '../../lib/doc_title';
@@ -19,17 +17,5 @@ export interface Props {
export const HomeApp: FC = ({ onLoad = () => {} }) => {
onLoad();
setDocTitle('Canvas');
- return (
-
-
-
- {}} />
-
-
-
- );
+ return ;
};
diff --git a/x-pack/plugins/canvas/public/components/toolbar/__stories__/toolbar.stories.tsx b/x-pack/plugins/canvas/public/components/toolbar/__stories__/toolbar.stories.tsx
index e4f297446701c..bd47bb52e0030 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/__stories__/toolbar.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/__stories__/toolbar.stories.tsx
@@ -18,7 +18,6 @@ storiesOf('components/Toolbar', module)
isWriteable={true}
selectedPageNumber={1}
totalPages={1}
- workpadId={'abc'}
workpadName={'My Canvas Workpad'}
/>
))
@@ -28,7 +27,6 @@ storiesOf('components/Toolbar', module)
selectedElement={getDefaultElement()}
selectedPageNumber={1}
totalPages={1}
- workpadId={'abc'}
workpadName={'My Canvas Workpad'}
/>
));
diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
index baafbdafcc549..9e89ad4c4f27b 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
@@ -7,17 +7,8 @@
import React, { FC, useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiModal,
- EuiModalFooter,
- EuiButton,
-} from '@elastic/eui';
+import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-// @ts-expect-error untyped local
-import { WorkpadManager } from '../workpad_manager';
import { PageManager } from '../page_manager';
import { Expression } from '../expression';
import { Tray } from './tray';
@@ -37,7 +28,6 @@ export interface Props {
selectedElement?: CanvasElement;
selectedPageNumber: number;
totalPages: number;
- workpadId: string;
workpadName: string;
}
@@ -46,11 +36,9 @@ export const Toolbar: FC = ({
selectedElement,
selectedPageNumber,
totalPages,
- workpadId,
workpadName,
}) => {
const [activeTray, setActiveTray] = useState(null);
- const [showWorkpadManager, setShowWorkpadManager] = useState(false);
const { getUrl, previousPage } = useContext(WorkpadRoutingContext);
// While the tray doesn't get activated if the workpad isn't writeable,
@@ -75,20 +63,6 @@ export const Toolbar: FC = ({
}
};
- const closeWorkpadManager = () => setShowWorkpadManager(false);
- const openWorkpadManager = () => setShowWorkpadManager(true);
-
- const workpadManager = (
-
-
-
-
- {strings.getWorkpadManagerCloseButtonLabel()}
-
-
-
- );
-
const trays = {
pageManager: ,
expression: !elementIsSelected ? null : setActiveTray(null)} />,
@@ -99,12 +73,6 @@ export const Toolbar: FC = ({
{activeTray !== null && setActiveTray(null)}>{trays[activeTray]} }
-
- openWorkpadManager()}>
- {workpadName}
-
-
-
= ({
)}
- {showWorkpadManager && workpadManager}
);
};
@@ -153,6 +120,5 @@ Toolbar.propTypes = {
selectedElement: PropTypes.object,
selectedPageNumber: PropTypes.number.isRequired,
totalPages: PropTypes.number.isRequired,
- workpadId: PropTypes.string.isRequired,
workpadName: PropTypes.string.isRequired,
};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/index.tsx b/x-pack/plugins/canvas/public/components/workpad_loader/index.tsx
deleted file mode 100644
index 2afd5fe70abe1..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/index.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { FC, useState, useCallback } from 'react';
-import { useHistory } from 'react-router-dom';
-import { useSelector } from 'react-redux';
-import moment from 'moment';
-// @ts-expect-error
-import { getDefaultWorkpad } from '../../state/defaults';
-import { canUserWrite as canUserWriteSelector } from '../../state/selectors/app';
-import { getWorkpad } from '../../state/selectors/workpad';
-import { getId } from '../../lib/get_id';
-import { downloadWorkpad } from '../../lib/download_workpad';
-import { ComponentStrings, ErrorStrings } from '../../../i18n';
-import { State, CanvasWorkpad } from '../../../types';
-import { useNotifyService, useWorkpadService, usePlatformService } from '../../services';
-// @ts-expect-error
-import { WorkpadLoader as Component } from './workpad_loader';
-
-const { WorkpadLoader: strings } = ComponentStrings;
-const { WorkpadLoader: errors } = ErrorStrings;
-
-type WorkpadStatePromise = ReturnType['find']>;
-type WorkpadState = WorkpadStatePromise extends PromiseLike ? U : never;
-
-export const WorkpadLoader: FC<{ onClose: () => void }> = ({ onClose }) => {
- const fromState = useSelector((state: State) => ({
- workpadId: getWorkpad(state).id,
- canUserWrite: canUserWriteSelector(state),
- }));
-
- const [workpadsState, setWorkpadsState] = useState(null);
- const workpadService = useWorkpadService();
- const notifyService = useNotifyService();
- const platformService = usePlatformService();
- const history = useHistory();
-
- const createWorkpad = useCallback(
- async (_workpad: CanvasWorkpad | null | undefined) => {
- const workpad = _workpad || getDefaultWorkpad();
- if (workpad != null) {
- try {
- await workpadService.create(workpad);
- history.push(`/workpad/${workpad.id}/page/1`);
- } catch (err) {
- notifyService.error(err, {
- title: errors.getUploadFailureErrorMessage(),
- });
- }
- return;
- }
- },
- [workpadService, notifyService, history]
- );
-
- const findWorkpads = useCallback(
- async (text) => {
- try {
- const fetchedWorkpads = await workpadService.find(text);
- setWorkpadsState(fetchedWorkpads);
- } catch (err) {
- notifyService.error(err, { title: errors.getFindFailureErrorMessage() });
- }
- },
- [notifyService, workpadService]
- );
-
- const onDownloadWorkpad = useCallback((workpadId: string) => downloadWorkpad(workpadId), []);
-
- const cloneWorkpad = useCallback(
- async (workpadId: string) => {
- try {
- const workpad = await workpadService.get(workpadId);
- workpad.name = strings.getClonedWorkpadName(workpad.name);
- workpad.id = getId('workpad');
- await workpadService.create(workpad);
- history.push(`/workpad/${workpad.id}/page/1`);
- } catch (err) {
- notifyService.error(err, { title: errors.getCloneFailureErrorMessage() });
- }
- },
- [notifyService, workpadService, history]
- );
-
- const removeWorkpads = useCallback(
- (workpadIds: string[]) => {
- if (workpadsState === null) {
- return;
- }
-
- const removedWorkpads = workpadIds.map(async (id) => {
- try {
- await workpadService.remove(id);
- return { id, err: null };
- } catch (err) {
- return { id, err };
- }
- });
-
- return Promise.all(removedWorkpads).then((results) => {
- let redirectHome = false;
-
- const [passes, errored] = results.reduce<[string[], string[]]>(
- ([passesArr, errorsArr], result) => {
- if (result.id === fromState.workpadId && !result.err) {
- redirectHome = true;
- }
-
- if (result.err) {
- errorsArr.push(result.id);
- } else {
- passesArr.push(result.id);
- }
-
- return [passesArr, errorsArr];
- },
- [[], []]
- );
-
- const remainingWorkpads = workpadsState.workpads.filter(({ id }) => !passes.includes(id));
-
- const workpadState = {
- total: remainingWorkpads.length,
- workpads: remainingWorkpads,
- };
-
- if (errored.length > 0) {
- notifyService.error(errors.getDeleteFailureErrorMessage());
- }
-
- setWorkpadsState(workpadState);
-
- if (redirectHome) {
- history.push('/');
- }
-
- return errored;
- });
- },
- [history, workpadService, fromState.workpadId, workpadsState, notifyService]
- );
-
- const formatDate = useCallback(
- (date: any) => {
- const dateFormat = platformService.getUISetting('dateFormat');
- return date && moment(date).format(dateFormat);
- },
- [platformService]
- );
-
- const { workpadId, canUserWrite } = fromState;
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/upload_workpad.js b/x-pack/plugins/canvas/public/components/workpad_loader/upload_workpad.js
deleted file mode 100644
index 24a694268e4ee..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/upload_workpad.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { get } from 'lodash';
-import { getId } from '../../lib/get_id';
-import { ErrorStrings } from '../../../i18n';
-
-const { WorkpadFileUpload: errors } = ErrorStrings;
-
-export const uploadWorkpad = (file, onUpload, notify) => {
- if (!file) {
- return;
- }
-
- if (get(file, 'type') !== 'application/json') {
- return notify.warning(errors.getAcceptJSONOnlyErrorMessage(), {
- title: file.name
- ? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
- : errors.getFileUploadFailureWithoutFileNameErrorMessage(),
- });
- }
- // TODO: Clean up this file, this loading stuff can, and should be, abstracted
- const reader = new FileReader();
-
- // handle reading the uploaded file
- reader.onload = () => {
- try {
- const workpad = JSON.parse(reader.result);
- workpad.id = getId('workpad');
-
- // sanity check for workpad object
- if (!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets) {
- throw new Error(errors.getMissingPropertiesErrorMessage());
- }
-
- onUpload(workpad);
- } catch (e) {
- notify.error(e, {
- title: file.name
- ? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
- : errors.getFileUploadFailureWithoutFileNameErrorMessage(),
- });
- }
- };
-
- // read the uploaded file
- reader.readAsText(file);
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js
deleted file mode 100644
index 51733dad5b377..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { EuiButton } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n';
-
-const { WorkpadCreate: strings } = ComponentStrings;
-
-export const WorkpadCreate = ({ createPending, onCreate, ...rest }) => (
-
- {strings.getWorkpadCreateButtonLabel()}
-
-);
-
-WorkpadCreate.propTypes = {
- onCreate: PropTypes.func.isRequired,
- createPending: PropTypes.bool,
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js
deleted file mode 100644
index 7c34837771c6f..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import PropTypes from 'prop-types';
-import { compose, withHandlers } from 'recompose';
-import { uploadWorkpad } from '../upload_workpad';
-import { ErrorStrings } from '../../../../i18n';
-import { WorkpadDropzone as Component } from './workpad_dropzone';
-
-const { WorkpadFileUpload: errors } = ErrorStrings;
-
-export const WorkpadDropzone = compose(
- withHandlers(({ notify }) => ({
- onDropAccepted: ({ onUpload }) => ([file]) => uploadWorkpad(file, onUpload),
- onDropRejected: () => ([file]) => {
- notify.warning(errors.getAcceptJSONOnlyErrorMessage(), {
- title: file.name
- ? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
- : errors.getFileUploadFailureWithoutFileNameErrorMessage(),
- });
- },
- }))
-)(Component);
-
-WorkpadDropzone.propTypes = {
- onUpload: PropTypes.func.isRequired,
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js
deleted file mode 100644
index f77929e1feb76..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import Dropzone from 'react-dropzone';
-
-export const WorkpadDropzone = ({ onDropAccepted, onDropRejected, disabled, children }) => (
-
- {children}
-
-);
-
-WorkpadDropzone.propTypes = {
- onDropAccepted: PropTypes.func.isRequired,
- onDropRejected: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired,
- children: PropTypes.node.isRequired,
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.scss b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.scss
deleted file mode 100644
index ac6838da97fbd..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.canvasWorkpad__dropzone {
- border: 2px dashed transparent;
-}
-
-.canvasWorkpad__dropzone--active {
- background-color: $euiColorLightestShade;
- border-color: $euiColorLightShade;
-}
-
-.canvasWorkpad__dropzoneTable .euiTable {
- background-color: transparent;
-}
-
-.canvasWorkpad__dropzoneTable--tags {
- .euiTableCellContent {
- flex-wrap: wrap;
- }
-
- .euiHealth {
- width: 100%;
- }
-}
\ No newline at end of file
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js
deleted file mode 100644
index 9c232ab43ec8d..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiBasicTable,
- EuiButtonIcon,
- EuiPagination,
- EuiSpacer,
- EuiButton,
- EuiToolTip,
- EuiEmptyPrompt,
- EuiFilePicker,
- EuiLink,
-} from '@elastic/eui';
-import { orderBy } from 'lodash';
-import { ConfirmModal } from '../confirm_modal';
-import { RoutingLink } from '../routing';
-import { Paginate } from '../paginate';
-import { ComponentStrings } from '../../../i18n';
-import { WorkpadDropzone } from './workpad_dropzone';
-import { WorkpadCreate } from './workpad_create';
-import { WorkpadSearch } from './workpad_search';
-import { uploadWorkpad } from './upload_workpad';
-
-const { WorkpadLoader: strings } = ComponentStrings;
-
-const getDisplayName = (name, workpad, loadedWorkpad) => {
- const workpadName = name.length ? name : {workpad.id} ;
- return workpad.id === loadedWorkpad ? {workpadName} : workpadName;
-};
-
-export class WorkpadLoader extends React.PureComponent {
- static propTypes = {
- workpadId: PropTypes.string.isRequired,
- canUserWrite: PropTypes.bool.isRequired,
- createWorkpad: PropTypes.func.isRequired,
- findWorkpads: PropTypes.func.isRequired,
- downloadWorkpad: PropTypes.func.isRequired,
- cloneWorkpad: PropTypes.func.isRequired,
- removeWorkpads: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired,
- workpads: PropTypes.object,
- formatDate: PropTypes.func.isRequired,
- };
-
- state = {
- createPending: false,
- deletingWorkpad: false,
- sortField: '@timestamp',
- sortDirection: 'desc',
- selectedWorkpads: [],
- pageSize: 10,
- };
-
- async componentDidMount() {
- // on component load, kick off the workpad search
- this.props.findWorkpads();
-
- // keep track of whether or not the component is mounted, to prevent rogue setState calls
- this._isMounted = true;
- }
-
- UNSAFE_componentWillReceiveProps(newProps) {
- // the workpadId prop will change when a is created or loaded, close the toolbar when it does
- const { workpadId, onClose } = this.props;
- if (workpadId !== newProps.workpadId) {
- onClose();
- }
- }
-
- componentWillUnmount() {
- this._isMounted = false;
- }
-
- // create new empty workpad
- createWorkpad = async () => {
- this.setState({ createPending: true });
- await this.props.createWorkpad();
- this._isMounted && this.setState({ createPending: false });
- };
-
- // create new workpad from uploaded JSON
- onUpload = async (workpad) => {
- this.setState({ createPending: true });
- await this.props.createWorkpad(workpad);
- this._isMounted && this.setState({ createPending: false });
- };
-
- // clone existing workpad
- cloneWorkpad = async (workpad) => {
- this.setState({ createPending: true });
- await this.props.cloneWorkpad(workpad.id);
- this._isMounted && this.setState({ createPending: false });
- };
-
- // Workpad remove methods
- openRemoveConfirm = () => this.setState({ deletingWorkpad: true });
-
- closeRemoveConfirm = () => this.setState({ deletingWorkpad: false });
-
- removeWorkpads = () => {
- const { selectedWorkpads } = this.state;
-
- this.props.removeWorkpads(selectedWorkpads.map(({ id }) => id)).then((remainingIds) => {
- const remainingWorkpads =
- remainingIds.length > 0
- ? selectedWorkpads.filter(({ id }) => remainingIds.includes(id))
- : [];
-
- this._isMounted &&
- this.setState({
- deletingWorkpad: false,
- selectedWorkpads: remainingWorkpads,
- });
- });
- };
-
- // downloads selected workpads as JSON files
- downloadWorkpads = () => {
- this.state.selectedWorkpads.forEach(({ id }) => this.props.downloadWorkpad(id));
- };
-
- onSelectionChange = (selectedWorkpads) => {
- this.setState({ selectedWorkpads });
- };
-
- onTableChange = ({ sort = {} }) => {
- const { field: sortField, direction: sortDirection } = sort;
- this.setState({
- sortField,
- sortDirection,
- });
- };
-
- renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }) => {
- const { sortField, sortDirection } = this.state;
- const { canUserWrite, createPending, workpadId: loadedWorkpad } = this.props;
-
- const actions = [
- {
- render: (workpad) => (
-
-
-
- this.props.downloadWorkpad(workpad.id)}
- aria-label={strings.getExportToolTip()}
- />
-
-
-
-
- this.cloneWorkpad(workpad)}
- aria-label={strings.getCloneToolTip()}
- disabled={!canUserWrite}
- />
-
-
-
- ),
- },
- ];
-
- const columns = [
- {
- field: 'name',
- name: strings.getTableNameColumnTitle(),
- sortable: true,
- dataType: 'string',
- render: (name, workpad) => {
- const workpadName = getDisplayName(name, workpad, loadedWorkpad);
-
- return (
-
- {workpadName}
-
- );
- },
- },
- {
- field: '@created',
- name: strings.getTableCreatedColumnTitle(),
- sortable: true,
- dataType: 'date',
- width: '20%',
- render: (date) => this.props.formatDate(date),
- },
- {
- field: '@timestamp',
- name: strings.getTableUpdatedColumnTitle(),
- sortable: true,
- dataType: 'date',
- width: '20%',
- render: (date) => this.props.formatDate(date),
- },
- { name: strings.getTableActionsColumnTitle(), actions, width: '100px' },
- ];
-
- const sorting = {
- sort: {
- field: sortField,
- direction: sortDirection,
- },
- };
-
- const selection = {
- itemId: 'id',
- onSelectionChange: this.onSelectionChange,
- };
-
- const emptyTable = (
- {strings.getEmptyPromptTitle()}}
- titleSize="s"
- body={
-
- {strings.getEmptyPromptGettingStartedDescription()}
-
- {strings.getEmptyPromptNewUserDescription()}{' '}
-
- {strings.getSampleDataLinkLabel()}
-
- .
-
-
- }
- />
- );
-
- return (
-
-
-
-
- {rows.length > 0 && (
-
-
-
-
-
- )}
-
-
- );
- };
-
- render() {
- const {
- deletingWorkpad,
- createPending,
- selectedWorkpads,
- sortField,
- sortDirection,
- } = this.state;
- const { canUserWrite } = this.props;
- const isLoading = this.props.workpads == null;
-
- let createButton = (
-
- );
-
- let deleteButton = (
-
- {strings.getDeleteButtonLabel(selectedWorkpads.length)}
-
- );
-
- const downloadButton = (
-
- {strings.getExportButtonLabel(selectedWorkpads.length)}
-
- );
-
- let uploadButton = (
- uploadWorkpad(file, this.onUpload, this.props.notify)}
- accept="application/json"
- disabled={createPending || !canUserWrite}
- />
- );
-
- if (!canUserWrite) {
- createButton = (
- {createButton}
- );
- deleteButton = (
- {deleteButton}
- );
- uploadButton = (
- {uploadButton}
- );
- }
-
- const modalTitle =
- selectedWorkpads.length === 1
- ? strings.getDeleteSingleWorkpadModalTitle(selectedWorkpads[0].name)
- : strings.getDeleteMultipleWorkpadModalTitle(selectedWorkpads.length);
-
- const confirmModal = (
-
- );
-
- let sortedWorkpads = [];
-
- if (!createPending && !isLoading) {
- const { workpads } = this.props.workpads;
- sortedWorkpads = orderBy(workpads, [sortField, '@timestamp'], [sortDirection, 'desc']);
- }
-
- return (
-
- {(pagination) => (
-
-
-
-
- {selectedWorkpads.length > 0 && (
-
- {downloadButton}
- {deleteButton}
-
- )}
-
- {
- pagination.setPage(0);
- this.props.findWorkpads(text);
- }}
- />
-
-
-
-
-
- {uploadButton}
- {createButton}
-
-
-
-
-
-
- {createPending && (
- {strings.getCreateWorkpadLoadingDescription()}
- )}
-
- {!createPending && isLoading && (
- {strings.getFetchLoadingDescription()}
- )}
-
- {!createPending && !isLoading && this.renderWorkpadTable(pagination)}
-
- {confirmModal}
-
- )}
-
- );
- }
-}
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.scss b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.scss
deleted file mode 100644
index 3b2c8eae9e542..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-.canvasWorkpad__upload--compressed {
-
- &.euiFilePicker--compressed.euiFilePicker {
- .euiFilePicker__prompt {
- height: $euiSizeXXL;
- padding: $euiSizeM;
- padding-left: $euiSizeXXL;
- }
-
- .euiFilePicker__icon {
- top: $euiSizeM;
- }
- }
-
- // The file picker input is being used moreso as a button, outside of a form,
- // and thus the need to override the default max-width of form inputs.
- // An issue has been opened in EUI to consider creating a button
- // version of the file picker - https://github.com/elastic/eui/issues/1987
-
- .euiFilePicker__wrap {
- @include euiBreakpoint('xs', 's') {
- max-width: none;
- }
- }
-}
\ No newline at end of file
diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js
deleted file mode 100644
index 8bf8bbae8ced4..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_search.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { EuiFieldSearch } from '@elastic/eui';
-import { debounce } from 'lodash';
-import { ComponentStrings } from '../../../i18n';
-
-const { WorkpadSearch: strings } = ComponentStrings;
-export class WorkpadSearch extends React.PureComponent {
- static propTypes = {
- onChange: PropTypes.func.isRequired,
- initialText: PropTypes.string,
- };
-
- state = {
- searchText: this.props.initialText || '',
- };
-
- triggerChange = debounce(this.props.onChange, 150);
-
- setSearchText = (ev) => {
- const text = ev.target.value;
- this.setState({ searchText: text });
- this.triggerChange(text);
- };
-
- render() {
- return (
-
- );
- }
-}
diff --git a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js b/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js
deleted file mode 100644
index 8055be32ac481..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import {
- EuiTabbedContent,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiModalBody,
- EuiSpacer,
- EuiFlexGroup,
- EuiFlexItem,
-} from '@elastic/eui';
-import { WorkpadLoader } from '../workpad_loader';
-import { WorkpadTemplates } from '../workpad_templates';
-import { ComponentStrings } from '../../../i18n';
-
-const { WorkpadManager: strings } = ComponentStrings;
-
-export const WorkpadManager = ({ onClose }) => {
- const tabs = [
- {
- id: 'workpadLoader',
- name: strings.getMyWorkpadsTabLabel(),
- content: (
-
-
-
-
- ),
- },
- {
- id: 'workpadTemplates',
- name: strings.getWorkpadTemplatesTabLabel(),
- 'data-test-subj': 'workpadTemplates',
- content: (
-
-
-
-
- ),
- },
- ];
- return (
-
-
-
-
-
- {strings.getModalTitle()}
-
-
-
-
-
-
-
-
- );
-};
-
-WorkpadManager.propTypes = {
- onClose: PropTypes.func,
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
deleted file mode 100644
index cab6e8fd9b5f5..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
+++ /dev/null
@@ -1,564 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots components/WorkpadTemplates default 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- Tags
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sorting
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Template name
-
-
-
-
-
-
-
-
- Description
-
-
-
-
-
-
- Tags
-
-
-
-
-
-
-
-
-
- Template name
-
-
-
-
-
- test1
-
-
-
-
-
-
-
- Description
-
-
-
- This is a test template
-
-
-
-
-
- Tags
-
-
-
-
-
-
-
- Template name
-
-
-
-
-
- test2
-
-
-
-
-
-
-
- Description
-
-
-
- This is a second test template
-
-
-
-
-
- Tags
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/workpad_templates.stories.tsx b/x-pack/plugins/canvas/public/components/workpad_templates/examples/workpad_templates.stories.tsx
deleted file mode 100644
index 8e6c055478ca2..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/workpad_templates.stories.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import { WorkpadTemplates } from '../workpad_templates';
-import { CanvasTemplate } from '../../../../types';
-
-const templates: Record = {
- test1: {
- id: 'test1-id',
- name: 'test1',
- help: 'This is a test template',
- tags: ['tag1', 'tag2'],
- template_key: 'test1-key',
- },
- test2: {
- id: 'test2-id',
- name: 'test2',
- help: 'This is a second test template',
- tags: ['tag2', 'tag3'],
- template_key: 'test2-key',
- },
-};
-
-storiesOf('components/WorkpadTemplates', module)
- .addDecorator((story) => {story()}
)
- .add('default', () => {
- const onCreateFromTemplateAction = action('onCreateFromTemplate');
- return (
- {
- onCreateFromTemplateAction(template);
- return Promise.resolve();
- }}
- />
- );
- });
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/index.tsx b/x-pack/plugins/canvas/public/components/workpad_templates/index.tsx
deleted file mode 100644
index 7e007b1253464..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_templates/index.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useCallback, useState, useEffect, FunctionComponent } from 'react';
-import { EuiLoadingSpinner } from '@elastic/eui';
-import { useHistory } from 'react-router-dom';
-
-import { ComponentStrings } from '../../../i18n/components';
-// @ts-expect-error
-import * as workpadService from '../../lib/workpad_service';
-import { WorkpadTemplates as Component } from './workpad_templates';
-import { CanvasTemplate } from '../../../types';
-import { list } from '../../lib/template_service';
-import { applyTemplateStrings } from '../../../i18n/templates/apply_strings';
-import { useNotifyService, useServices } from '../../services';
-
-interface WorkpadTemplatesProps {
- onClose: () => void;
-}
-
-const Creating: FunctionComponent<{ name: string }> = ({ name }) => (
-
- {' '}
- {ComponentStrings.WorkpadTemplates.getCreatingTemplateLabel(name)}
-
-);
-export const WorkpadTemplates: FunctionComponent = ({ onClose }) => {
- const history = useHistory();
- const services = useServices();
-
- const [templates, setTemplates] = useState(undefined);
- const [creatingFromTemplateName, setCreatingFromTemplateName] = useState(
- undefined
- );
- const { error } = useNotifyService();
-
- useEffect(() => {
- if (!templates) {
- (async () => {
- const fetchedTemplates = await list();
- setTemplates(applyTemplateStrings(fetchedTemplates));
- })();
- }
- }, [templates]);
-
- let templateProp: Record = {};
-
- if (templates) {
- templateProp = templates.reduce>((reduction, template) => {
- reduction[template.name] = template;
- return reduction;
- }, {});
- }
-
- const createFromTemplate = useCallback(
- async (template: CanvasTemplate) => {
- setCreatingFromTemplateName(template.name);
- try {
- const result = await services.workpad.createFromTemplate(template.id);
- history.push(`/workpad/${result.id}/page/1`);
- } catch (e) {
- setCreatingFromTemplateName(undefined);
- error(e, {
- title: `Couldn't create workpad from template`,
- });
- }
- },
- [services.workpad, error, history]
- );
-
- if (creatingFromTemplateName) {
- return ;
- }
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.tsx b/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.tsx
deleted file mode 100644
index 72871b93c1735..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_templates/workpad_templates.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiBasicTable,
- EuiPagination,
- EuiSpacer,
- EuiButtonEmpty,
- EuiSearchBar,
- EuiTableSortingType,
- Direction,
- SortDirection,
-} from '@elastic/eui';
-import { orderBy } from 'lodash';
-// @ts-ignore untyped local
-import { EuiBasicTableColumn } from '@elastic/eui';
-import { Paginate, PaginateChildProps } from '../paginate';
-import { TagList } from '../tag_list';
-import { getTagsFilter } from '../../lib/get_tags_filter';
-// @ts-expect-error
-import { extractSearch } from '../../lib/extract_search';
-import { ComponentStrings } from '../../../i18n';
-import { CanvasTemplate } from '../../../types';
-
-interface TableChange {
- page?: {
- index: number;
- size: number;
- };
- sort?: {
- field: keyof T;
- direction: Direction;
- };
-}
-
-const { WorkpadTemplates: strings } = ComponentStrings;
-
-interface WorkpadTemplatesProps {
- onCreateFromTemplate: (template: CanvasTemplate) => Promise;
- onClose: () => void;
- templates: Record;
-}
-
-interface WorkpadTemplatesState {
- sortField: string;
- sortDirection: Direction;
- pageSize: number;
- searchTerm: string;
- filterTags: string[];
-}
-
-export class WorkpadTemplates extends React.PureComponent<
- WorkpadTemplatesProps,
- WorkpadTemplatesState
-> {
- static propTypes = {
- onCreateFromTemplate: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired,
- templates: PropTypes.object,
- };
-
- state = {
- sortField: 'name',
- sortDirection: SortDirection.ASC,
- pageSize: 10,
- searchTerm: '',
- filterTags: [],
- };
-
- tagType: 'health' = 'health';
-
- onTableChange = (tableChange: TableChange) => {
- if (tableChange.sort) {
- const { field: sortField, direction: sortDirection } = tableChange.sort;
- this.setState({
- sortField,
- sortDirection,
- });
- }
- };
-
- onSearch = ({ queryText = '' }) => this.setState(extractSearch(queryText));
-
- cloneTemplate = (template: CanvasTemplate) =>
- this.props.onCreateFromTemplate(template).then(() => this.props.onClose());
-
- renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }: PaginateChildProps) => {
- const { sortField, sortDirection } = this.state;
-
- const columns: Array> = [
- {
- field: 'name',
- name: strings.getTableNameColumnTitle(),
- sortable: true,
- width: '30%',
- dataType: 'string',
- render: (name: string, template) => {
- const templateName = name.length ? name : 'Unnamed Template';
-
- return (
- this.cloneTemplate(template)}
- aria-label={strings.getCloneTemplateLinkAriaLabel(templateName)}
- type="button"
- >
- {templateName}
-
- );
- },
- },
- {
- field: 'help',
- name: strings.getTableDescriptionColumnTitle(),
- sortable: false,
- dataType: 'string',
- width: '30%',
- },
- {
- field: 'tags',
- name: strings.getTableTagsColumnTitle(),
- sortable: false,
- dataType: 'string',
- width: '30%',
- render: (tags: string[]) => ,
- },
- ];
-
- const sorting: EuiTableSortingType = {
- sort: {
- field: sortField,
- direction: sortDirection,
- },
- };
-
- return (
-
-
-
- {rows.length > 0 && (
-
-
-
-
-
- )}
-
- );
- };
-
- renderSearch = () => {
- const { searchTerm } = this.state;
- const filters = [getTagsFilter(this.tagType)];
-
- return (
-
- );
- };
-
- render() {
- const { templates } = this.props;
- const { sortField, sortDirection, searchTerm, filterTags } = this.state;
- const sortedTemplates = orderBy(templates, [sortField, 'name'], [sortDirection, 'asc']);
-
- const filteredTemplates = sortedTemplates.filter(({ name = '', help = '', tags = [] }) => {
- const tagMatch = filterTags.length
- ? filterTags.every((filterTag) => tags.indexOf(filterTag) > -1)
- : true;
-
- const lowercaseSearch = searchTerm.toLowerCase();
- const textMatch = lowercaseSearch
- ? name.toLowerCase().indexOf(lowercaseSearch) > -1 ||
- help.toLowerCase().indexOf(lowercaseSearch) > -1
- : true;
-
- return tagMatch && textMatch;
- });
-
- return (
-
- {(pagination: PaginateChildProps) => (
-
- {this.renderSearch()}
-
- {this.renderWorkpadTable(pagination)}
-
- )}
-
- );
- }
-}
diff --git a/x-pack/plugins/canvas/public/lib/get_tags_filter.tsx b/x-pack/plugins/canvas/public/lib/get_tags_filter.tsx
deleted file mode 100644
index 12d77c9c7f0c0..0000000000000
--- a/x-pack/plugins/canvas/public/lib/get_tags_filter.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { sortBy } from 'lodash';
-import { SearchFilterConfig } from '@elastic/eui';
-import { Tag } from '../components/tag';
-import { getId } from './get_id';
-import { tagsRegistry } from './tags_registry';
-import { ComponentStrings } from '../../i18n';
-
-const { WorkpadTemplates: strings } = ComponentStrings;
-
-// EUI helper function
-// generates the FieldValueSelectionFilter object for EuiSearchBar for tag filtering
-export const getTagsFilter = (type: 'health' | 'badge'): SearchFilterConfig => {
- const uniqueTags = sortBy(Object.values(tagsRegistry.toJS()), 'name');
- const filterType = 'field_value_selection';
-
- return {
- type: filterType,
- field: 'tag',
- name: strings.getTableTagsColumnTitle(),
- multiSelect: true,
- options: uniqueTags.map(({ name, color }) => ({
- value: name,
- name,
- view: (
-
-
-
- ),
- })),
- };
-};
diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts
index 6c039660c64c7..3f8f58367171a 100644
--- a/x-pack/plugins/canvas/public/services/index.ts
+++ b/x-pack/plugins/canvas/public/services/index.ts
@@ -34,7 +34,7 @@ export type CanvasServiceFactory = (
appUpdater: BehaviorSubject
) => Service | Promise;
-class CanvasServiceProvider {
+export class CanvasServiceProvider {
private factory: CanvasServiceFactory;
private service: Service | undefined;
diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts
index ea80a5a7c26b9..5776a1d0d6983 100644
--- a/x-pack/plugins/canvas/public/services/stubs/platform.ts
+++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts
@@ -9,13 +9,19 @@ import { PlatformService } from '../platform';
const noop = (..._args: any[]): any => {};
+const uiSettings: Record = {
+ dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
+};
+
+const getUISetting = (setting: string) => uiSettings[setting];
+
export const platformService: PlatformService = {
getBasePath: () => '/base/path',
getBasePathInterface: noop,
getDocLinkVersion: () => 'dockLinkVersion',
getElasticWebsiteUrl: () => 'https://elastic.co',
getHasWriteAccess: () => true,
- getUISetting: noop,
+ getUISetting,
setBreadcrumbs: noop,
setRecentlyAccessed: noop,
getSavedObjects: noop,
diff --git a/x-pack/plugins/canvas/public/services/stubs/workpad.ts b/x-pack/plugins/canvas/public/services/stubs/workpad.ts
index 857831c92a8a6..4e3612feb67c8 100644
--- a/x-pack/plugins/canvas/public/services/stubs/workpad.ts
+++ b/x-pack/plugins/canvas/public/services/stubs/workpad.ts
@@ -5,17 +5,95 @@
* 2.0.
*/
+import moment from 'moment';
+
+// @ts-expect-error
+import { getDefaultWorkpad } from '../../state/defaults';
import { WorkpadService } from '../workpad';
-import { CanvasWorkpad } from '../../../types';
+import { getId } from '../../lib/get_id';
+import { CanvasTemplate } from '../../../types';
-export const workpadService: WorkpadService = {
- get: (id: string) => Promise.resolve({} as CanvasWorkpad),
- create: (workpad) => Promise.resolve({} as CanvasWorkpad),
- createFromTemplate: (templateId: string) => Promise.resolve({} as CanvasWorkpad),
- find: (term: string) =>
- Promise.resolve({
+const TIMEOUT = 500;
+
+const promiseTimeout = (time: number) => () => new Promise((resolve) => setTimeout(resolve, time));
+const getName = () => {
+ const lorem = 'Lorem ipsum dolor sit amet consectetur adipiscing elit Fusce lobortis aliquet arcu ut turpis duis'.split(
+ ' '
+ );
+ return [1, 2, 3].map(() => lorem[Math.floor(Math.random() * lorem.length)]).join(' ');
+};
+
+const randomDate = (
+ start: Date = moment().toDate(),
+ end: Date = moment().subtract(7, 'days').toDate()
+) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString();
+
+const templates: CanvasTemplate[] = [
+ {
+ id: 'test1-id',
+ name: 'test1',
+ help: 'This is a test template',
+ tags: ['tag1', 'tag2'],
+ template_key: 'test1-key',
+ },
+ {
+ id: 'test2-id',
+ name: 'test2',
+ help: 'This is a second test template',
+ tags: ['tag2', 'tag3'],
+ template_key: 'test2-key',
+ },
+];
+
+export const getSomeWorkpads = (count = 3) =>
+ Array.from({ length: count }, () => ({
+ '@created': randomDate(
+ moment().subtract(3, 'days').toDate(),
+ moment().subtract(10, 'days').toDate()
+ ),
+ '@timestamp': randomDate(),
+ id: getId('workpad'),
+ name: getName(),
+ }));
+
+export const findSomeWorkpads = (count = 3, timeout = TIMEOUT) => (_term: string) => {
+ return Promise.resolve()
+ .then(promiseTimeout(timeout))
+ .then(() => ({
+ total: count,
+ workpads: getSomeWorkpads(count),
+ }));
+};
+
+export const findNoWorkpads = (timeout = TIMEOUT) => (_term: string) => {
+ return Promise.resolve()
+ .then(promiseTimeout(timeout))
+ .then(() => ({
total: 0,
workpads: [],
- }),
- remove: (id: string) => Promise.resolve(undefined),
+ }));
+};
+
+export const findSomeTemplates = (timeout = TIMEOUT) => () => {
+ return Promise.resolve()
+ .then(promiseTimeout(timeout))
+ .then(() => getSomeTemplates());
+};
+
+export const findNoTemplates = (timeout = TIMEOUT) => () => {
+ return Promise.resolve()
+ .then(promiseTimeout(timeout))
+ .then(() => getNoTemplates());
+};
+
+export const getNoTemplates = () => ({ templates: [] });
+export const getSomeTemplates = () => ({ templates });
+
+export const workpadService: WorkpadService = {
+ get: (id: string) => Promise.resolve({ ...getDefaultWorkpad(), id }),
+ findTemplates: findNoTemplates(),
+ create: (workpad) => Promise.resolve(workpad),
+ createFromTemplate: (_templateId: string) => Promise.resolve(getDefaultWorkpad()),
+ find: findNoWorkpads(),
+ remove: (id: string) => Promise.resolve(),
};
diff --git a/x-pack/plugins/canvas/public/services/workpad.ts b/x-pack/plugins/canvas/public/services/workpad.ts
index 11690ca4c0c45..7d2f1550a312f 100644
--- a/x-pack/plugins/canvas/public/services/workpad.ts
+++ b/x-pack/plugins/canvas/public/services/workpad.ts
@@ -5,8 +5,12 @@
* 2.0.
*/
-import { API_ROUTE_WORKPAD, DEFAULT_WORKPAD_CSS } from '../../common/lib/constants';
-import { CanvasWorkpad } from '../../types';
+import {
+ API_ROUTE_WORKPAD,
+ DEFAULT_WORKPAD_CSS,
+ API_ROUTE_TEMPLATES,
+} from '../../common/lib/constants';
+import { CanvasWorkpad, CanvasTemplate } from '../../types';
import { CanvasServiceFactory } from './';
/*
@@ -40,9 +44,15 @@ const sanitizeWorkpad = function (workpad: CanvasWorkpad) {
return workpad;
};
-interface WorkpadFindResponse {
+export type FoundWorkpads = Array>;
+export type FoundWorkpad = FoundWorkpads[number];
+export interface WorkpadFindResponse {
total: number;
- workpads: Array>;
+ workpads: FoundWorkpads;
+}
+
+export interface TemplateFindResponse {
+ templates: CanvasTemplate[];
}
export interface WorkpadService {
@@ -51,6 +61,7 @@ export interface WorkpadService {
createFromTemplate: (templateId: string) => Promise;
find: (term: string) => Promise;
remove: (id: string) => Promise;
+ findTemplates: () => Promise;
}
export const workpadServiceFactory: CanvasServiceFactory = (
@@ -82,7 +93,9 @@ export const workpadServiceFactory: CanvasServiceFactory = (
body: JSON.stringify({ templateId }),
});
},
+ findTemplates: async () => coreStart.http.get(API_ROUTE_TEMPLATES),
find: (searchTerm: string) => {
+ // TODO: this shouldn't be necessary. Check for usage.
const validSearchTerm = typeof searchTerm === 'string' && searchTerm.length > 0;
return coreStart.http.get(`${getApiPath()}/find`, {
diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss
index a79e07a7d0016..d9592d5c0be5f 100644
--- a/x-pack/plugins/canvas/public/style/index.scss
+++ b/x-pack/plugins/canvas/public/style/index.scss
@@ -40,8 +40,6 @@
@import '../components/workpad_header/element_menu/element_menu';
@import '../components/workpad_header/share_menu/share_menu';
@import '../components/workpad_header/view_menu/view_menu';
-@import '../components/workpad_loader/workpad_loader';
-@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
@import '../components/workpad_page/workpad_page';
@import '../components/workpad_page/workpad_interactive_page/workpad_interactive_page';
@import '../components/workpad_page/workpad_static_page/workpad_static_page';
diff --git a/x-pack/plugins/canvas/storybook/decorators/index.ts b/x-pack/plugins/canvas/storybook/decorators/index.ts
index a674eaad576a7..598a2333be554 100644
--- a/x-pack/plugins/canvas/storybook/decorators/index.ts
+++ b/x-pack/plugins/canvas/storybook/decorators/index.ts
@@ -11,6 +11,7 @@ import { kibanaContextDecorator } from './kibana_decorator';
import { servicesContextDecorator } from './services_decorator';
export { reduxDecorator } from './redux_decorator';
+export { servicesContextDecorator } from './services_decorator';
export const addDecorators = () => {
if (process.env.NODE_ENV === 'test') {
@@ -20,5 +21,5 @@ export const addDecorators = () => {
addDecorator(kibanaContextDecorator);
addDecorator(routerContextDecorator);
- addDecorator(servicesContextDecorator);
+ addDecorator(servicesContextDecorator());
};
diff --git a/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx
index 01d96cb0c70e6..289171f136ab5 100644
--- a/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx
+++ b/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx
@@ -25,7 +25,7 @@ elementsRegistry.register(image);
import { getInitialState, getReducer, getMiddleware, patchDispatch } from '../addon/src/state';
export { ADDON_ID, ACTIONS_PANEL_ID } from '../addon/src/constants';
-interface Params {
+export interface Params {
workpad?: CanvasWorkpad;
elements?: CanvasElement[];
assets?: CanvasAsset[];
diff --git a/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
index a11492387ea7f..def5a5681a8c4 100644
--- a/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
+++ b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
@@ -7,8 +7,40 @@
import React from 'react';
-import { ServicesProvider } from '../../public/services';
+import {
+ CanvasServiceFactory,
+ CanvasServiceProvider,
+ ServicesProvider,
+} from '../../public/services';
+import {
+ findNoWorkpads,
+ findSomeWorkpads,
+ workpadService,
+ findSomeTemplates,
+ findNoTemplates,
+} from '../../public/services/stubs/workpad';
+import { WorkpadService } from '../../public/services/workpad';
-export const servicesContextDecorator = (story: Function) => (
- {story()}
-);
+interface Params {
+ findWorkpads?: number;
+ findTemplates?: boolean;
+}
+
+export const servicesContextDecorator = ({
+ findWorkpads = 0,
+ findTemplates: findTemplatesOption = false,
+}: Params = {}) => {
+ const workpadServiceFactory: CanvasServiceFactory = (): WorkpadService => ({
+ ...workpadService,
+ find: findWorkpads > 0 ? findSomeWorkpads(findWorkpads) : findNoWorkpads(),
+ findTemplates: findTemplatesOption ? findSomeTemplates() : findNoTemplates(),
+ });
+
+ const workpad = new CanvasServiceProvider(workpadServiceFactory);
+ // @ts-expect-error This is a hack at the moment, until we can get Canvas moved over to the new services architecture.
+ workpad.start();
+
+ return (story: Function) => (
+ {story()}
+ );
+};
diff --git a/x-pack/plugins/canvas/storybook/index.ts b/x-pack/plugins/canvas/storybook/index.ts
index 148af337d7720..ff60b84c88a69 100644
--- a/x-pack/plugins/canvas/storybook/index.ts
+++ b/x-pack/plugins/canvas/storybook/index.ts
@@ -10,3 +10,8 @@ import { ACTIONS_PANEL_ID } from './addon/src/constants';
export * from './decorators';
export { ACTIONS_PANEL_ID } from './addon/src/constants';
export const getAddonPanelParameters = () => ({ options: { selectedPanel: ACTIONS_PANEL_ID } });
+export const getDisableStoryshotsParameter = () => ({
+ storyshots: {
+ disable: true,
+ },
+});
diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts
index 80a8aeb14a804..69c05322cf3f0 100644
--- a/x-pack/plugins/canvas/storybook/main.ts
+++ b/x-pack/plugins/canvas/storybook/main.ts
@@ -53,6 +53,11 @@ const canvasWebpack = {
},
],
},
+ resolve: {
+ alias: {
+ 'src/plugins': resolve(KIBANA_ROOT, 'src/plugins'),
+ },
+ },
};
module.exports = {
diff --git a/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot b/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
new file mode 100644
index 0000000000000..39ec1e234ead5
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
@@ -0,0 +1,65 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Home/Empty Prompt Empty Prompt 1`] = `
+
+
+
+
+
+
+
+
+ Add your first workpad
+
+
+
+
+ Create a new workpad, start from a template, or import a workpad JSON file by dropping it here.
+
+
+ New to Canvas?
+
+
+ Add your first workpad
+
+ .
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.tsx b/x-pack/plugins/canvas/storybook/storyshots.test.tsx
index 0c3765812066e..7f0ea077c7569 100644
--- a/x-pack/plugins/canvas/storybook/storyshots.test.tsx
+++ b/x-pack/plugins/canvas/storybook/storyshots.test.tsx
@@ -90,6 +90,11 @@ import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer'
jest.mock('@elastic/eui/test-env/components/observer/observer');
EuiObserver.mockImplementation(() => 'EuiObserver');
+// @ts-expect-error untyped library
+import Dropzone from 'react-dropzone';
+jest.mock('react-dropzone');
+Dropzone.mockImplementation(() => 'Dropzone');
+
// This element uses a `ref` and cannot be rendered by Jest snapshots.
import { RenderedElement } from '../shareable_runtime/components/rendered_element';
jest.mock('../shareable_runtime/components/rendered_element');
@@ -111,7 +116,7 @@ addSerializer(styleSheetSerializer);
// Initialize Storyshots and build the Jest Snapshots
initStoryshots({
- configPath: path.resolve(__dirname, './../storybook'),
+ configPath: path.resolve(__dirname),
framework: 'react',
test: multiSnapshotWithOptions({}),
// Don't snapshot tests that start with 'redux'
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 91277403d9e05..bc8318e803c8f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -6107,18 +6107,18 @@
"xpack.canvas.error.esService.indicesFetchErrorMessage": "Elasticsearch インデックスを取得できませんでした",
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "「{functionName}」のレンダリングが失敗しました",
"xpack.canvas.error.repeatImage.missingMaxArgument": "{emptyImageArgument} を指定する場合は、{maxArgument} を設定する必要があります",
- "xpack.canvas.error.workpadLoader.cloneFailureErrorMessage": "ワークパッドのクローンを作成できませんでした",
- "xpack.canvas.error.workpadLoader.deleteFailureErrorMessage": "すべてのワークパッドを削除できませんでした",
- "xpack.canvas.error.workpadLoader.findFailureErrorMessage": "ワークパッドが見つかりませんでした",
- "xpack.canvas.error.workpadLoader.uploadFailureErrorMessage": "ワークパッドをアップロードできませんでした",
+ "xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "ワークパッドのクローンを作成できませんでした",
+ "xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "ワークパッドをアップロードできませんでした",
+ "xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "すべてのワークパッドを削除できませんでした",
+ "xpack.canvas.error.useFindWorkpads.findFailureErrorMessage": "ワークパッドが見つかりませんでした",
+ "xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage": "{JSON} 個のファイルしか受け付けられませんでした",
+ "xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage": "ファイルをアップロードできませんでした",
+ "xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} ワークパッドに必要なプロパティの一部が欠けています。 {JSON} ファイルを編集して正しいプロパティ値を入力し、再試行してください。",
"xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "ワークパッドを作成できませんでした",
"xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "ID でワークパッドを読み込めませんでした",
- "xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage": "{JSON} 個のファイルしか受け付けられませんでした",
- "xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage": "ファイルをアップロードできませんでした",
- "xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage": "{CANVAS} ワークパッドに必要なプロパティの一部が欠けています。 {JSON} ファイルを編集して正しいプロパティ値を入力し、再試行してください。",
"xpack.canvas.errorComponent.description": "表現が失敗し次のメッセージが返されました:",
"xpack.canvas.errorComponent.title": "おっと!表現が失敗しました",
- "xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage": "「{fileName}」をアップロードできませんでした",
+ "xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "「{fileName}」をアップロードできませんでした",
"xpack.canvas.expression.cancelButtonLabel": "キャンセル",
"xpack.canvas.expression.closeButtonLabel": "閉じる",
"xpack.canvas.expression.learnLinkText": "表現構文の詳細",
@@ -6452,6 +6452,12 @@
"xpack.canvas.helpMenu.description": "{CANVAS} に関する情報",
"xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} ドキュメント",
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "キーボードショートカット",
+ "xpack.canvas.home.myWorkpadsTabLabel": "マイワークパッド",
+ "xpack.canvas.home.workpadTemplatesTabLabel": "テンプレート",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription": "新規ワークパッドを作成、テンプレートで開始、またはワークパッド {JSON} ファイルをここにドロップしてインポートします。",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription": "{CANVAS} を初めて使用する場合",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptTitle": "初の’ワークパッドを追加しましょう",
+ "xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel": "初の’ワークパッドを追加しましょう",
"xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText": "前に移動",
"xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText": "表面に移動",
"xpack.canvas.keyboardShortcuts.cloneShortcutHelpText": "クローンを作成",
@@ -6898,6 +6904,7 @@
"xpack.canvas.units.quickRange.last90Days": "過去90日間",
"xpack.canvas.units.quickRange.today": "今日",
"xpack.canvas.units.quickRange.yesterday": "昨日",
+ "xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} のコピー",
"xpack.canvas.varConfig.addButtonLabel": "変数の追加",
"xpack.canvas.varConfig.addTooltipLabel": "変数の追加",
"xpack.canvas.varConfig.copyActionButtonLabel": "スニペットをコピー",
@@ -7024,40 +7031,30 @@
"xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "ズーム",
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "リセット",
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
- "xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} のコピー",
- "xpack.canvas.workpadLoader.cloneTooltip": "ワークパッドのクローンを作成します",
- "xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "ワークパッドを作成中...",
- "xpack.canvas.workpadLoader.deleteButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドを削除",
- "xpack.canvas.workpadLoader.deleteButtonLabel": " ({numberOfWorkpads}) ワークパッドを削除",
- "xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel": "削除",
- "xpack.canvas.workpadLoader.deleteModalDescription": "削除されたワークパッドは復元できません。",
- "xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle": "{numberOfWorkpads} 個のワークパッドを削除しますか?",
- "xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle": "ワークパッド「{workpadName}」削除しますか?",
- "xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription": "新規ワークパッドを作成、テンプレートで開始、またはワークパッド {JSON} ファイルをここにドロップしてインポートします。",
- "xpack.canvas.workpadLoader.emptyPromptNewUserDescription": "{CANVAS} を初めて使用する場合",
- "xpack.canvas.workpadLoader.emptyPromptTitle": "初の’ワークパッドを追加しましょう",
- "xpack.canvas.workpadLoader.exportButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドをエクスポート",
- "xpack.canvas.workpadLoader.exportButtonLabel": "エクスポート ({numberOfWorkpads}) ",
- "xpack.canvas.workpadLoader.exportTooltip": "ワークパッドをエクスポート",
- "xpack.canvas.workpadLoader.fetchLoadingDescription": "ワークパッドを取得中...",
- "xpack.canvas.workpadLoader.filePickerPlaceholder": "ワークパッド {JSON} ファイルをインポート",
- "xpack.canvas.workpadLoader.loadWorkpadArialLabel": "ワークパッド「{workpadName}」を読み込む",
- "xpack.canvas.workpadLoader.noPermissionToCloneToolTip": "ワークパッドのクローンを作成するパーミッションがありません",
- "xpack.canvas.workpadLoader.noPermissionToCreateToolTip": "ワークパッドを作成するパーミッションがありません",
- "xpack.canvas.workpadLoader.noPermissionToDeleteToolTip": "ワークパッドを削除するパーミッションがありません",
- "xpack.canvas.workpadLoader.noPermissionToUploadToolTip": "ワークパッドを更新するパーミッションがありません",
- "xpack.canvas.workpadLoader.sampleDataLinkLabel": "初の’ワークパッドを追加しましょう",
- "xpack.canvas.workpadLoader.table.actionsColumnTitle": "アクション",
- "xpack.canvas.workpadLoader.table.createdColumnTitle": "作成済み",
- "xpack.canvas.workpadLoader.table.nameColumnTitle": "ワークパッド名",
- "xpack.canvas.workpadLoader.table.updatedColumnTitle": "更新しました",
- "xpack.canvas.workpadManager.modalTitle": "{CANVAS} ワークパッド",
- "xpack.canvas.workpadManager.myWorkpadsTabLabel": "マイワークパッド",
- "xpack.canvas.workpadManager.workpadTemplatesTabLabel": "テンプレート",
- "xpack.canvas.workpadSearch.searchPlaceholder": "ワークパッドを検索",
- "xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel": "ワークパッドテンプレート「{templateName}」のクローンを作成",
- "xpack.canvas.workpadTemplate.creatingTemplateLabel": "テンプレート「{templateName}」から作成しています",
- "xpack.canvas.workpadTemplate.searchPlaceholder": "テンプレートを検索",
+ "xpack.canvas.workpadImport.filePickerPlaceholder": "ワークパッド {JSON} ファイルをインポート",
+ "xpack.canvas.workpadTable.cloneTooltip": "ワークパッドのクローンを作成します",
+ "xpack.canvas.workpadTable.exportTooltip": "ワークパッドをエクスポート",
+ "xpack.canvas.workpadTable.loadWorkpadArialLabel": "ワークパッド「{workpadName}」を読み込む",
+ "xpack.canvas.workpadTable.noPermissionToCloneToolTip": "ワークパッドのクローンを作成するパーミッションがありません",
+ "xpack.canvas.workpadTable.searchPlaceholder": "ワークパッドを検索",
+ "xpack.canvas.workpadTable.table.actionsColumnTitle": "アクション",
+ "xpack.canvas.workpadTable.table.createdColumnTitle": "作成済み",
+ "xpack.canvas.workpadTable.table.nameColumnTitle": "ワークパッド名",
+ "xpack.canvas.workpadTable.table.updatedColumnTitle": "更新しました",
+ "xpack.canvas.workpadTableTools.deleteButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドを削除",
+ "xpack.canvas.workpadTableTools.deleteButtonLabel": " ({numberOfWorkpads}) ワークパッドを削除",
+ "xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel": "削除",
+ "xpack.canvas.workpadTableTools.deleteModalDescription": "削除されたワークパッドは復元できません。",
+ "xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle": "{numberOfWorkpads} 個のワークパッドを削除しますか?",
+ "xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle": "ワークパッド「{workpadName}」削除しますか?",
+ "xpack.canvas.workpadTableTools.exportButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドをエクスポート",
+ "xpack.canvas.workpadTableTools.exportButtonLabel": "エクスポート ({numberOfWorkpads}) ",
+ "xpack.canvas.workpadTableTools.noPermissionToCreateToolTip": "ワークパッドを作成するパーミッションがありません",
+ "xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip": "ワークパッドを削除するパーミッションがありません",
+ "xpack.canvas.workpadTableTools.noPermissionToUploadToolTip": "ワークパッドを更新するパーミッションがありません",
+ "xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel": "ワークパッドテンプレート「{templateName}」のクローンを作成",
+ "xpack.canvas.workpadTemplates.creatingTemplateLabel": "テンプレート「{templateName}」から作成しています",
+ "xpack.canvas.workpadTemplates.searchPlaceholder": "テンプレートを検索",
"xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "説明",
"xpack.canvas.workpadTemplates.table.nameColumnTitle": "テンプレート名",
"xpack.canvas.workpadTemplates.table.tagsColumnTitle": "タグ",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 632c502d4ef55..f867407ff2d9b 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -6146,18 +6146,18 @@
"xpack.canvas.error.esService.indicesFetchErrorMessage": "无法提取 Elasticsearch 索引",
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "呈现“{functionName}”失败。",
"xpack.canvas.error.repeatImage.missingMaxArgument": "如果提供 {emptyImageArgument},则必须设置 {maxArgument}",
- "xpack.canvas.error.workpadLoader.cloneFailureErrorMessage": "无法克隆 Workpad",
- "xpack.canvas.error.workpadLoader.deleteFailureErrorMessage": "无法删除所有 Workpad",
- "xpack.canvas.error.workpadLoader.findFailureErrorMessage": "无法查找 Workpad",
- "xpack.canvas.error.workpadLoader.uploadFailureErrorMessage": "无法上传 Workpad",
+ "xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "无法克隆 Workpad",
+ "xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "无法上传 Workpad",
+ "xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "无法删除所有 Workpad",
+ "xpack.canvas.error.useFindWorkpads.findFailureErrorMessage": "无法查找 Workpad",
+ "xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage": "仅接受 {JSON} 文件",
+ "xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage": "无法上传文件",
+ "xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} Workpad 所需的某些属性缺失。 编辑 {JSON} 文件以提供正确的属性值,然后重试。",
"xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "无法创建 Workpad",
"xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "无法加载具有以下 ID 的 Workpad",
- "xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage": "仅接受 {JSON} 文件",
- "xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage": "无法上传文件",
- "xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage": "{CANVAS} Workpad 所需的某些属性缺失。 编辑 {JSON} 文件以提供正确的属性值,然后重试。",
"xpack.canvas.errorComponent.description": "表达式失败,并显示消息:",
"xpack.canvas.errorComponent.title": "哎哟!表达式失败",
- "xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage": "无法上传“{fileName}”",
+ "xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "无法上传“{fileName}”",
"xpack.canvas.expression.cancelButtonLabel": "取消",
"xpack.canvas.expression.closeButtonLabel": "关闭",
"xpack.canvas.expression.learnLinkText": "学习表达式语法",
@@ -6492,6 +6492,12 @@
"xpack.canvas.helpMenu.description": "有关 {CANVAS} 特定信息",
"xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} 文档",
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "快捷键",
+ "xpack.canvas.home.myWorkpadsTabLabel": "我的 Workpad",
+ "xpack.canvas.home.workpadTemplatesTabLabel": "模板",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription": "创建新的 Workpad、从模板入手或通过将 Workpad {JSON} 文件拖放到此处来导入。",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription": "{CANVAS} 新手?",
+ "xpack.canvas.homeEmptyPrompt.emptyPromptTitle": "添加您的首个 Workpad",
+ "xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel": "添加您的首个 Workpad",
"xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText": "前移",
"xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText": "置前",
"xpack.canvas.keyboardShortcuts.cloneShortcutHelpText": "克隆",
@@ -6942,6 +6948,7 @@
"xpack.canvas.units.time.hours": "{hours, plural, other {# 小时}}",
"xpack.canvas.units.time.minutes": "{minutes, plural, other {# 分钟}}",
"xpack.canvas.units.time.seconds": "{seconds, plural, other {# 秒}}",
+ "xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} 副本",
"xpack.canvas.varConfig.addButtonLabel": "添加变量",
"xpack.canvas.varConfig.addTooltipLabel": "添加变量",
"xpack.canvas.varConfig.copyActionButtonLabel": "复制代码片段",
@@ -7072,40 +7079,30 @@
"xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "缩放",
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "重置",
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
- "xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} 副本",
- "xpack.canvas.workpadLoader.cloneTooltip": "克隆 Workpad",
- "xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "正在创建 Workpad......",
- "xpack.canvas.workpadLoader.deleteButtonAriaLabel": "删除 {numberOfWorkpads} 个 Workpad",
- "xpack.canvas.workpadLoader.deleteButtonLabel": "删除 ({numberOfWorkpads})",
- "xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel": "删除",
- "xpack.canvas.workpadLoader.deleteModalDescription": "您无法恢复删除的 Workpad。",
- "xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle": "删除 {numberOfWorkpads} 个 Workpad?",
- "xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle": "删除 Workpad“{workpadName}”?",
- "xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription": "创建新的 Workpad、从模板入手或通过将 Workpad {JSON} 文件拖放到此处来导入。",
- "xpack.canvas.workpadLoader.emptyPromptNewUserDescription": "{CANVAS} 新手?",
- "xpack.canvas.workpadLoader.emptyPromptTitle": "添加您的首个 Workpad",
- "xpack.canvas.workpadLoader.exportButtonAriaLabel": "导出 {numberOfWorkpads} 个 Workpad",
- "xpack.canvas.workpadLoader.exportButtonLabel": "导出 ({numberOfWorkpads})",
- "xpack.canvas.workpadLoader.exportTooltip": "导出 Workpad",
- "xpack.canvas.workpadLoader.fetchLoadingDescription": "正在获取 Workpad......",
- "xpack.canvas.workpadLoader.filePickerPlaceholder": "导入 Workpad {JSON} 文件",
- "xpack.canvas.workpadLoader.loadWorkpadArialLabel": "加载 Workpad“{workpadName}”",
- "xpack.canvas.workpadLoader.noPermissionToCloneToolTip": "您无权克隆 Workpad",
- "xpack.canvas.workpadLoader.noPermissionToCreateToolTip": "您无权创建 Workpad",
- "xpack.canvas.workpadLoader.noPermissionToDeleteToolTip": "您无权删除 Workpad",
- "xpack.canvas.workpadLoader.noPermissionToUploadToolTip": "您无权上传 Workpad",
- "xpack.canvas.workpadLoader.sampleDataLinkLabel": "添加您的首个 Workpad",
- "xpack.canvas.workpadLoader.table.actionsColumnTitle": "操作",
- "xpack.canvas.workpadLoader.table.createdColumnTitle": "创建时间",
- "xpack.canvas.workpadLoader.table.nameColumnTitle": "Workpad 名称",
- "xpack.canvas.workpadLoader.table.updatedColumnTitle": "更新时间",
- "xpack.canvas.workpadManager.modalTitle": "{CANVAS} Workpad",
- "xpack.canvas.workpadManager.myWorkpadsTabLabel": "我的 Workpad",
- "xpack.canvas.workpadManager.workpadTemplatesTabLabel": "模板",
- "xpack.canvas.workpadSearch.searchPlaceholder": "查找 Workpad",
- "xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel": "克隆 Workpad 模板“{templateName}”",
- "xpack.canvas.workpadTemplate.creatingTemplateLabel": "正在从模板“{templateName}”创建",
- "xpack.canvas.workpadTemplate.searchPlaceholder": "查找模板",
+ "xpack.canvas.workpadImport.filePickerPlaceholder": "导入 Workpad {JSON} 文件",
+ "xpack.canvas.workpadTable.searchPlaceholder": "查找 Workpad",
+ "xpack.canvas.workpadTable.cloneTooltip": "克隆 Workpad",
+ "xpack.canvas.workpadTable.exportTooltip": "导出 Workpad",
+ "xpack.canvas.workpadTable.loadWorkpadArialLabel": "加载 Workpad“{workpadName}”",
+ "xpack.canvas.workpadTable.noPermissionToCloneToolTip": "您无权克隆 Workpad",
+ "xpack.canvas.workpadTable.table.actionsColumnTitle": "操作",
+ "xpack.canvas.workpadTable.table.createdColumnTitle": "创建时间",
+ "xpack.canvas.workpadTable.table.nameColumnTitle": "Workpad 名称",
+ "xpack.canvas.workpadTable.table.updatedColumnTitle": "更新时间",
+ "xpack.canvas.workpadTableTools.deleteButtonAriaLabel": "删除 {numberOfWorkpads} 个 Workpad",
+ "xpack.canvas.workpadTableTools.deleteButtonLabel": "删除 ({numberOfWorkpads})",
+ "xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel": "删除",
+ "xpack.canvas.workpadTableTools.deleteModalDescription": "您无法恢复删除的 Workpad。",
+ "xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle": "删除 {numberOfWorkpads} 个 Workpad?",
+ "xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle": "删除 Workpad“{workpadName}”?",
+ "xpack.canvas.workpadTableTools.exportButtonAriaLabel": "导出 {numberOfWorkpads} 个 Workpad",
+ "xpack.canvas.workpadTableTools.exportButtonLabel": "导出 ({numberOfWorkpads})",
+ "xpack.canvas.workpadTableTools.noPermissionToCreateToolTip": "您无权创建 Workpad",
+ "xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip": "您无权删除 Workpad",
+ "xpack.canvas.workpadTableTools.noPermissionToUploadToolTip": "您无权上传 Workpad",
+ "xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel": "克隆 Workpad 模板“{templateName}”",
+ "xpack.canvas.workpadTemplates.creatingTemplateLabel": "正在从模板“{templateName}”创建",
+ "xpack.canvas.workpadTemplates.searchPlaceholder": "查找模板",
"xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "描述",
"xpack.canvas.workpadTemplates.table.nameColumnTitle": "模板名称",
"xpack.canvas.workpadTemplates.table.tagsColumnTitle": "标签",
diff --git a/x-pack/test/accessibility/apps/canvas.ts b/x-pack/test/accessibility/apps/canvas.ts
index a79fb7b60e76a..609c8bf5bb1ae 100644
--- a/x-pack/test/accessibility/apps/canvas.ts
+++ b/x-pack/test/accessibility/apps/canvas.ts
@@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('loads workpads', async function () {
await retry.waitFor(
'canvas workpads visible',
- async () => await testSubjects.exists('canvasWorkpadLoaderTable')
+ async () => await testSubjects.exists('canvasWorkpadTable')
);
await a11y.testAppSnapshot();
});
diff --git a/x-pack/test/functional/apps/canvas/smoke_test.js b/x-pack/test/functional/apps/canvas/smoke_test.js
index 5280ad0118fba..fcc04aafdbcd8 100644
--- a/x-pack/test/functional/apps/canvas/smoke_test.js
+++ b/x-pack/test/functional/apps/canvas/smoke_test.js
@@ -17,7 +17,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) {
describe('smoke test', function () {
this.tags('includeFirefox');
- const workpadListSelector = 'canvasWorkpadLoaderTable > canvasWorkpadLoaderWorkpad';
+ const workpadListSelector = 'canvasWorkpadTable > canvasWorkpadTableWorkpad';
const testWorkpadId = 'workpad-1705f884-6224-47de-ba49-ca224fe6ec31';
before(async () => {
diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts
index 0e0203046fd16..df92c1c398d93 100644
--- a/x-pack/test/functional/page_objects/canvas_page.ts
+++ b/x-pack/test/functional/page_objects/canvas_page.ts
@@ -39,7 +39,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo
* to load the workpad. Resolves once the workpad is in the DOM
*/
async loadFirstWorkpad(workpadName: string) {
- const elem = await testSubjects.find('canvasWorkpadLoaderWorkpad');
+ const elem = await testSubjects.find('canvasWorkpadTableWorkpad');
const text = await elem.getVisibleText();
expect(text).to.be(workpadName);
await elem.click();
From 86fb2cc90e38e5e9c783c94b9904387c6b06dea6 Mon Sep 17 00:00:00 2001
From: Patrick Mueller
Date: Tue, 22 Jun 2021 15:18:35 -0400
Subject: [PATCH 06/78] [actions] add rule saved object reference to action
execution event log doc (#101526)
resolves https://github.com/elastic/kibana/issues/99225
Prior to this PR, when an alerting connection action was executed, the event
log document generated did not contain a reference to the originating rule.
This makes it difficult to diagnose problems with connector errors, since
the error is often in the parameters specified in the actions in the alert.
In this PR, a reference to the alerting rule is added to the saved_objects
field in the event document for these events.
---
.../actions/server/actions_client.test.ts | 64 ++++++++++++++
.../plugins/actions/server/actions_client.ts | 9 +-
.../server/create_execute_function.test.ts | 56 ++++++++++++
.../actions/server/create_execute_function.ts | 5 +-
.../actions/server/lib/action_executor.ts | 13 +++
.../server/lib/related_saved_objects.test.ts | 86 +++++++++++++++++++
.../server/lib/related_saved_objects.ts | 31 +++++++
.../server/lib/task_runner_factory.test.ts | 76 ++++++++++++++++
.../actions/server/lib/task_runner_factory.ts | 4 +-
.../actions/server/routes/execute.test.ts | 2 +
.../plugins/actions/server/routes/execute.ts | 1 +
.../server/routes/legacy/execute.test.ts | 2 +
.../actions/server/routes/legacy/execute.ts | 1 +
.../server/saved_objects/mappings.json | 4 +
.../create_execution_handler.test.ts | 32 +++++++
.../task_runner/create_execution_handler.ts | 12 ++-
.../server/task_runner/task_runner.test.ts | 32 +++++++
x-pack/plugins/event_log/README.md | 29 ++++---
18 files changed, 442 insertions(+), 17 deletions(-)
create mode 100644 x-pack/plugins/actions/server/lib/related_saved_objects.test.ts
create mode 100644 x-pack/plugins/actions/server/lib/related_saved_objects.ts
diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts
index 3b91b07eb30f4..16388b2faf52e 100644
--- a/x-pack/plugins/actions/server/actions_client.test.ts
+++ b/x-pack/plugins/actions/server/actions_client.test.ts
@@ -1676,6 +1676,70 @@ describe('execute()', () => {
name: 'my name',
},
});
+
+ await expect(
+ actionsClient.execute({
+ actionId,
+ params: {
+ name: 'my name',
+ },
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ typeId: 'some-type-id',
+ type: 'some-type',
+ },
+ ],
+ })
+ ).resolves.toMatchObject({ status: 'ok', actionId });
+
+ expect(actionExecutor.execute).toHaveBeenCalledWith({
+ actionId,
+ request,
+ params: {
+ name: 'my name',
+ },
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ typeId: 'some-type-id',
+ type: 'some-type',
+ },
+ ],
+ });
+
+ await expect(
+ actionsClient.execute({
+ actionId,
+ params: {
+ name: 'my name',
+ },
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ typeId: 'some-type-id',
+ type: 'some-type',
+ namespace: 'some-namespace',
+ },
+ ],
+ })
+ ).resolves.toMatchObject({ status: 'ok', actionId });
+
+ expect(actionExecutor.execute).toHaveBeenCalledWith({
+ actionId,
+ request,
+ params: {
+ name: 'my name',
+ },
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ typeId: 'some-type-id',
+ type: 'some-type',
+ namespace: 'some-namespace',
+ },
+ ],
+ });
});
});
diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts
index 449d218ed5ae0..f8d13cdafa755 100644
--- a/x-pack/plugins/actions/server/actions_client.ts
+++ b/x-pack/plugins/actions/server/actions_client.ts
@@ -469,6 +469,7 @@ export class ActionsClient {
actionId,
params,
source,
+ relatedSavedObjects,
}: Omit): Promise> {
if (
(await getAuthorizationModeBySource(this.unsecuredSavedObjectsClient, source)) ===
@@ -476,7 +477,13 @@ export class ActionsClient {
) {
await this.authorization.ensureAuthorized('execute');
}
- return this.actionExecutor.execute({ actionId, params, source, request: this.request });
+ return this.actionExecutor.execute({
+ actionId,
+ params,
+ source,
+ request: this.request,
+ relatedSavedObjects,
+ });
}
public async enqueueExecution(options: EnqueueExecutionOptions): Promise {
diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts
index 4cacba6dc880a..ee8064d2aadc5 100644
--- a/x-pack/plugins/actions/server/create_execute_function.test.ts
+++ b/x-pack/plugins/actions/server/create_execute_function.test.ts
@@ -83,6 +83,62 @@ describe('execute()', () => {
});
});
+ test('schedules the action with all given parameters and relatedSavedObjects', async () => {
+ const actionTypeRegistry = actionTypeRegistryMock.create();
+ const executeFn = createExecutionEnqueuerFunction({
+ taskManager: mockTaskManager,
+ actionTypeRegistry,
+ isESOCanEncrypt: true,
+ preconfiguredActions: [],
+ });
+ savedObjectsClient.get.mockResolvedValueOnce({
+ id: '123',
+ type: 'action',
+ attributes: {
+ actionTypeId: 'mock-action',
+ },
+ references: [],
+ });
+ savedObjectsClient.create.mockResolvedValueOnce({
+ id: '234',
+ type: 'action_task_params',
+ attributes: {},
+ references: [],
+ });
+ await executeFn(savedObjectsClient, {
+ id: '123',
+ params: { baz: false },
+ spaceId: 'default',
+ apiKey: Buffer.from('123:abc').toString('base64'),
+ source: asHttpRequestExecutionSource(request),
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ namespace: 'some-namespace',
+ type: 'some-type',
+ typeId: 'some-typeId',
+ },
+ ],
+ });
+ expect(savedObjectsClient.create).toHaveBeenCalledWith(
+ 'action_task_params',
+ {
+ actionId: '123',
+ params: { baz: false },
+ apiKey: Buffer.from('123:abc').toString('base64'),
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ namespace: 'some-namespace',
+ type: 'some-type',
+ typeId: 'some-typeId',
+ },
+ ],
+ },
+ {}
+ );
+ });
+
test('schedules the action with all given parameters with a preconfigured action', async () => {
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts
index 4f3ffbef36c6e..7dcd66c711bdd 100644
--- a/x-pack/plugins/actions/server/create_execute_function.ts
+++ b/x-pack/plugins/actions/server/create_execute_function.ts
@@ -11,6 +11,7 @@ import { RawAction, ActionTypeRegistryContract, PreConfiguredAction } from './ty
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects';
import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor';
import { isSavedObjectExecutionSource } from './lib';
+import { RelatedSavedObjects } from './lib/related_saved_objects';
interface CreateExecuteFunctionOptions {
taskManager: TaskManagerStartContract;
@@ -23,6 +24,7 @@ export interface ExecuteOptions extends Pick {
request: KibanaRequest;
params: Record;
source?: ActionExecutionSource;
+ relatedSavedObjects?: RelatedSavedObjects;
}
export type ActionExecutorContract = PublicMethodsOf;
@@ -68,6 +70,7 @@ export class ActionExecutor {
params,
request,
source,
+ relatedSavedObjects,
}: ExecuteOptions): Promise> {
if (!this.isInitialized) {
throw new Error('ActionExecutor not initialized');
@@ -154,6 +157,16 @@ export class ActionExecutor {
},
};
+ for (const relatedSavedObject of relatedSavedObjects || []) {
+ event.kibana?.saved_objects?.push({
+ rel: SAVED_OBJECT_REL_PRIMARY,
+ type: relatedSavedObject.type,
+ id: relatedSavedObject.id,
+ type_id: relatedSavedObject.typeId,
+ namespace: relatedSavedObject.namespace,
+ });
+ }
+
eventLogger.startTiming(event);
let rawResult: ActionTypeExecutorResult;
try {
diff --git a/x-pack/plugins/actions/server/lib/related_saved_objects.test.ts b/x-pack/plugins/actions/server/lib/related_saved_objects.test.ts
new file mode 100644
index 0000000000000..8fd13d1375697
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/related_saved_objects.test.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { validatedRelatedSavedObjects } from './related_saved_objects';
+import { loggingSystemMock } from '../../../../../src/core/server/mocks';
+import { Logger } from '../../../../../src/core/server';
+
+const loggerMock = loggingSystemMock.createLogger();
+
+describe('related_saved_objects', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('validates valid objects', () => {
+ ensureValid(loggerMock, undefined);
+ ensureValid(loggerMock, []);
+ ensureValid(loggerMock, [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ },
+ ]);
+ ensureValid(loggerMock, [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ typeId: 'some-type-id',
+ },
+ ]);
+ ensureValid(loggerMock, [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ namespace: 'some-namespace',
+ },
+ ]);
+ ensureValid(loggerMock, [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ typeId: 'some-type-id',
+ namespace: 'some-namespace',
+ },
+ ]);
+ ensureValid(loggerMock, [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ },
+ {
+ id: 'some-id-2',
+ type: 'some-type-2',
+ },
+ ]);
+ });
+});
+
+it('handles invalid objects', () => {
+ ensureInvalid(loggerMock, 42);
+ ensureInvalid(loggerMock, {});
+ ensureInvalid(loggerMock, [{}]);
+ ensureInvalid(loggerMock, [{ id: 'some-id' }]);
+ ensureInvalid(loggerMock, [{ id: 42 }]);
+ ensureInvalid(loggerMock, [{ id: 'some-id', type: 'some-type', x: 42 }]);
+});
+
+function ensureValid(logger: Logger, savedObjects: unknown) {
+ const result = validatedRelatedSavedObjects(logger, savedObjects);
+ expect(result).toEqual(savedObjects === undefined ? [] : savedObjects);
+ expect(loggerMock.warn).not.toHaveBeenCalled();
+}
+
+function ensureInvalid(logger: Logger, savedObjects: unknown) {
+ const result = validatedRelatedSavedObjects(logger, savedObjects);
+ expect(result).toEqual([]);
+
+ const message = loggerMock.warn.mock.calls[0][0];
+ expect(message).toMatch(
+ /ignoring invalid related saved objects: expected value of type \[array\] but got/
+ );
+}
diff --git a/x-pack/plugins/actions/server/lib/related_saved_objects.ts b/x-pack/plugins/actions/server/lib/related_saved_objects.ts
new file mode 100644
index 0000000000000..160587a3a9a8b
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/related_saved_objects.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+import { Logger } from '../../../../../src/core/server';
+
+export type RelatedSavedObjects = TypeOf;
+
+const RelatedSavedObjectsSchema = schema.arrayOf(
+ schema.object({
+ namespace: schema.maybe(schema.string({ minLength: 1 })),
+ id: schema.string({ minLength: 1 }),
+ type: schema.string({ minLength: 1 }),
+ // optional; for SO types like action/alert that have type id's
+ typeId: schema.maybe(schema.string({ minLength: 1 })),
+ }),
+ { defaultValue: [] }
+);
+
+export function validatedRelatedSavedObjects(logger: Logger, data: unknown): RelatedSavedObjects {
+ try {
+ return RelatedSavedObjectsSchema.validate(data);
+ } catch (err) {
+ logger.warn(`ignoring invalid related saved objects: ${err.message}`);
+ return [];
+ }
+}
diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
index 229324c1f0df3..2292994e3ccfd 100644
--- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
+++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
@@ -126,6 +126,7 @@ test('executes the task by calling the executor with proper parameters', async (
expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
actionId: '2',
params: { baz: true },
+ relatedSavedObjects: [],
request: expect.objectContaining({
headers: {
// base64 encoded "123:abc"
@@ -247,6 +248,7 @@ test('uses API key when provided', async () => {
expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
actionId: '2',
params: { baz: true },
+ relatedSavedObjects: [],
request: expect.objectContaining({
headers: {
// base64 encoded "123:abc"
@@ -262,6 +264,79 @@ test('uses API key when provided', async () => {
);
});
+test('uses relatedSavedObjects when provided', async () => {
+ const taskRunner = taskRunnerFactory.create({
+ taskInstance: mockedTaskInstance,
+ });
+
+ mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' });
+ spaceIdToNamespace.mockReturnValueOnce('namespace-test');
+ mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({
+ id: '3',
+ type: 'action_task_params',
+ attributes: {
+ actionId: '2',
+ params: { baz: true },
+ apiKey: Buffer.from('123:abc').toString('base64'),
+ relatedSavedObjects: [{ id: 'some-id', type: 'some-type' }],
+ },
+ references: [],
+ });
+
+ await taskRunner.run();
+
+ expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
+ actionId: '2',
+ params: { baz: true },
+ relatedSavedObjects: [
+ {
+ id: 'some-id',
+ type: 'some-type',
+ },
+ ],
+ request: expect.objectContaining({
+ headers: {
+ // base64 encoded "123:abc"
+ authorization: 'ApiKey MTIzOmFiYw==',
+ },
+ }),
+ });
+});
+
+test('sanitizes invalid relatedSavedObjects when provided', async () => {
+ const taskRunner = taskRunnerFactory.create({
+ taskInstance: mockedTaskInstance,
+ });
+
+ mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' });
+ spaceIdToNamespace.mockReturnValueOnce('namespace-test');
+ mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({
+ id: '3',
+ type: 'action_task_params',
+ attributes: {
+ actionId: '2',
+ params: { baz: true },
+ apiKey: Buffer.from('123:abc').toString('base64'),
+ relatedSavedObjects: [{ Xid: 'some-id', type: 'some-type' }],
+ },
+ references: [],
+ });
+
+ await taskRunner.run();
+
+ expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
+ actionId: '2',
+ params: { baz: true },
+ relatedSavedObjects: [],
+ request: expect.objectContaining({
+ headers: {
+ // base64 encoded "123:abc"
+ authorization: 'ApiKey MTIzOmFiYw==',
+ },
+ }),
+ });
+});
+
test(`doesn't use API key when not provided`, async () => {
const factory = new TaskRunnerFactory(mockedActionExecutor);
factory.initialize(taskRunnerFactoryInitializerParams);
@@ -284,6 +359,7 @@ test(`doesn't use API key when not provided`, async () => {
expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
actionId: '2',
params: { baz: true },
+ relatedSavedObjects: [],
request: expect.objectContaining({
headers: {},
}),
diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts
index cf4b1576f2778..0515963ab82f4 100644
--- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts
+++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts
@@ -30,6 +30,7 @@ import {
} from '../types';
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from '../constants/saved_objects';
import { asSavedObjectExecutionSource } from './action_execution_source';
+import { validatedRelatedSavedObjects } from './related_saved_objects';
export interface TaskRunnerContext {
logger: Logger;
@@ -77,7 +78,7 @@ export class TaskRunnerFactory {
const namespace = spaceIdToNamespace(spaceId);
const {
- attributes: { actionId, params, apiKey },
+ attributes: { actionId, params, apiKey, relatedSavedObjects },
references,
} = await encryptedSavedObjectsClient.getDecryptedAsInternalUser(
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
@@ -117,6 +118,7 @@ export class TaskRunnerFactory {
actionId,
request: fakeRequest,
...getSourceFromReferences(references),
+ relatedSavedObjects: validatedRelatedSavedObjects(logger, relatedSavedObjects),
});
} catch (e) {
if (e instanceof ActionTypeDisabledError) {
diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/execute.test.ts
index 4b12bf3111c1f..54e10698e5af9 100644
--- a/x-pack/plugins/actions/server/routes/execute.test.ts
+++ b/x-pack/plugins/actions/server/routes/execute.test.ts
@@ -65,6 +65,7 @@ describe('executeActionRoute', () => {
someData: 'data',
},
source: asHttpRequestExecutionSource(req),
+ relatedSavedObjects: [],
});
expect(res.ok).toHaveBeenCalled();
@@ -101,6 +102,7 @@ describe('executeActionRoute', () => {
expect(actionsClient.execute).toHaveBeenCalledWith({
actionId: '1',
params: {},
+ relatedSavedObjects: [],
source: asHttpRequestExecutionSource(req),
});
diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts
index 377fe1215b3fb..7e8110365e87a 100644
--- a/x-pack/plugins/actions/server/routes/execute.ts
+++ b/x-pack/plugins/actions/server/routes/execute.ts
@@ -53,6 +53,7 @@ export const executeActionRoute = (
params,
actionId: id,
source: asHttpRequestExecutionSource(req),
+ relatedSavedObjects: [],
});
return body
? res.ok({
diff --git a/x-pack/plugins/actions/server/routes/legacy/execute.test.ts b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts
index 2ac53ddaaedf6..05b71819911a3 100644
--- a/x-pack/plugins/actions/server/routes/legacy/execute.test.ts
+++ b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts
@@ -63,6 +63,7 @@ describe('executeActionRoute', () => {
someData: 'data',
},
source: asHttpRequestExecutionSource(req),
+ relatedSavedObjects: [],
});
expect(res.ok).toHaveBeenCalled();
@@ -100,6 +101,7 @@ describe('executeActionRoute', () => {
actionId: '1',
params: {},
source: asHttpRequestExecutionSource(req),
+ relatedSavedObjects: [],
});
expect(res.ok).not.toHaveBeenCalled();
diff --git a/x-pack/plugins/actions/server/routes/legacy/execute.ts b/x-pack/plugins/actions/server/routes/legacy/execute.ts
index f6ddec1d01c20..d7ed8d2e15604 100644
--- a/x-pack/plugins/actions/server/routes/legacy/execute.ts
+++ b/x-pack/plugins/actions/server/routes/legacy/execute.ts
@@ -48,6 +48,7 @@ export const executeActionRoute = (
params,
actionId: id,
source: asHttpRequestExecutionSource(req),
+ relatedSavedObjects: [],
});
return body
? res.ok({
diff --git a/x-pack/plugins/actions/server/saved_objects/mappings.json b/x-pack/plugins/actions/server/saved_objects/mappings.json
index c598b96ba2451..57f801ae9a075 100644
--- a/x-pack/plugins/actions/server/saved_objects/mappings.json
+++ b/x-pack/plugins/actions/server/saved_objects/mappings.json
@@ -35,6 +35,10 @@
},
"apiKey": {
"type": "binary"
+ },
+ "relatedSavedObjects": {
+ "enabled": false,
+ "type": "object"
}
}
}
diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts
index 25f0656163f5d..033ffcceb6a0a 100644
--- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts
@@ -135,6 +135,14 @@ test('enqueues execution per selected action', async () => {
"foo": true,
"stateVal": "My goes here",
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": "test1",
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
@@ -247,6 +255,14 @@ test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () =>
id: '1',
type: 'alert',
}),
+ relatedSavedObjects: [
+ {
+ id: '1',
+ namespace: 'test1',
+ type: 'alert',
+ typeId: 'test',
+ },
+ ],
spaceId: 'test1',
apiKey: createExecutionHandlerParams.apiKey,
});
@@ -327,6 +343,14 @@ test('context attribute gets parameterized', async () => {
"foo": true,
"stateVal": "My goes here",
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": "test1",
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
@@ -360,6 +384,14 @@ test('state attribute gets parameterized', async () => {
"foo": true,
"stateVal": "My state-val goes here",
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": "test1",
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts
index c3a36297c217a..968fff540dc03 100644
--- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts
+++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts
@@ -157,6 +157,8 @@ export function createExecutionHandler<
continue;
}
+ const namespace = spaceId === 'default' ? {} : { namespace: spaceId };
+
// TODO would be nice to add the action name here, but it's not available
const actionLabel = `${action.actionTypeId}:${action.id}`;
const actionsClient = await actionsPlugin.getActionsClientWithRequest(request);
@@ -169,10 +171,16 @@ export function createExecutionHandler<
id: alertId,
type: 'alert',
}),
+ relatedSavedObjects: [
+ {
+ id: alertId,
+ type: 'alert',
+ namespace: namespace.namespace,
+ typeId: alertType.id,
+ },
+ ],
});
- const namespace = spaceId === 'default' ? {} : { namespace: spaceId };
-
const event: IEvent = {
event: {
action: EVENT_LOG_ACTIONS.executeAction,
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
index 39a45584631d2..8ab267a5610d3 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
@@ -352,6 +352,14 @@ describe('Task Runner', () => {
"params": Object {
"foo": true,
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
@@ -1098,6 +1106,14 @@ describe('Task Runner', () => {
"params": Object {
"foo": true,
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
@@ -1634,6 +1650,14 @@ describe('Task Runner', () => {
"params": Object {
"isResolved": true,
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
@@ -1826,6 +1850,14 @@ describe('Task Runner', () => {
"params": Object {
"isResolved": true,
},
+ "relatedSavedObjects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "type": "alert",
+ "typeId": "test",
+ },
+ ],
"source": Object {
"source": Object {
"id": "1",
diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md
index 032f77543acb9..ffbd20dd6f2be 100644
--- a/x-pack/plugins/event_log/README.md
+++ b/x-pack/plugins/event_log/README.md
@@ -131,7 +131,7 @@ Below is a document in the expected structure, with descriptions of the fields:
instance_id: "alert instance id, for relevant documents",
action_group_id: "alert action group, for relevant documents",
action_subgroup: "alert action subgroup, for relevant documents",
- status: "overall alert status, after alert execution",
+ status: "overall alert status, after rule execution",
},
saved_objects: [
{
@@ -160,21 +160,26 @@ plugins:
- `action: execute-via-http` - generated when an action is executed via HTTP request
- `provider: alerting`
- - `action: execute` - generated when an alert executor runs
- - `action: execute-action` - generated when an alert schedules an action to run
- - `action: new-instance` - generated when an alert has a new instance id that is active
- - `action: recovered-instance` - generated when an alert has a previously active instance id that is no longer active
- - `action: active-instance` - generated when an alert determines an instance id is active
+ - `action: execute` - generated when a rule executor runs
+ - `action: execute-action` - generated when a rule schedules an action to run
+ - `action: new-instance` - generated when a rule has a new instance id that is active
+ - `action: recovered-instance` - generated when a rule has a previously active instance id that is no longer active
+ - `action: active-instance` - generated when a rule determines an instance id is active
For the `saved_objects` array elements, these are references to saved objects
-associated with the event. For the `alerting` provider, those are alert saved
-ojects and for the `actions` provider those are action saved objects. The
-`alerts:execute-action` event includes both the alert and action saved object
-references. For that event, only the alert reference has the optional `rel`
+associated with the event. For the `alerting` provider, those are rule saved
+ojects and for the `actions` provider those are connector saved objects. The
+`alerts:execute-action` event includes both the rule and connector saved object
+references. For that event, only the rule reference has the optional `rel`
property with a `primary` value. This property is used when searching the
event log to indicate which saved objects should be directly searchable via
-saved object references. For the `alerts:execute-action` event, searching
-only via the alert saved object reference will return the event.
+saved object references. For the `alerts:execute-action` event, only searching
+via the rule saved object reference will return the event; searching via the
+connector save object reference will **NOT** return the event. The
+`actions:execute` event also includes both the rule and connector saved object
+references, and both of them have the `rel` property with a `primary` value,
+allowing those events to be returned in searches of either the rule or
+connector.
## Event Log index - associated resources
From b386ce149a8d2175005fbe5045ee48bf8c56f977 Mon Sep 17 00:00:00 2001
From: Constance
Date: Tue, 22 Jun 2021 12:27:27 -0700
Subject: [PATCH 07/78] [App Search] Convert Schema pages to new page template
(#102846)
* Convert Schema page to new page template
+ update empty state - remove panel wrapper, add create schema field modal
* Convert ReindexJob view to new page template
+ remove breadcrumb prop
* Convert Meta Engine Schema view to new page template
* Update routers
* [Polish] Misc Davey Schema UI tweaks
- see https://github.com/elastic/kibana/pull/101958/files
+ change color away from secondary, since that's going away in EUI at some point
* [UX] Fix SchemaAddFieldModal stuttering on first new schema field add
- With the new template, transitioning from the empty state to the filled schema state causes the modal to stutter due to the component rerender
- Changing the page to not instantly react/update `hasSchema` when local schema state changes but instead to wait for the server call to finish and for cachedSchema to update fixes the UX problem
* [UI polish] Revert button color change per Davey's feedback
---
.../components/engine/engine_router.tsx | 10 +-
.../schema/components/empty_state.test.tsx | 11 ++
.../schema/components/empty_state.tsx | 16 ++-
.../schema/reindex_job/reindex_job.test.tsx | 17 +--
.../schema/reindex_job/reindex_job.tsx | 61 ++++----
.../components/schema/schema_logic.test.ts | 8 +-
.../components/schema/schema_logic.ts | 5 +-
.../components/schema/schema_router.tsx | 12 +-
.../schema/views/meta_engine_schema.test.tsx | 10 +-
.../schema/views/meta_engine_schema.tsx | 131 +++++++++---------
.../components/schema/views/schema.test.tsx | 28 +---
.../components/schema/views/schema.tsx | 50 +++----
.../shared/schema/add_field_modal/index.tsx | 10 +-
13 files changed, 172 insertions(+), 197 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
index fc057858426d2..91a21847107a9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
@@ -109,6 +109,11 @@ export const EngineRouter: React.FC = () => {
)}
+ {canViewEngineSchema && (
+
+
+
+ )}
{canManageEngineSearchUi && (
@@ -121,11 +126,6 @@ export const EngineRouter: React.FC = () => {
)}
{/* TODO: Remove layout once page template migration is over */}
}>
- {canViewEngineSchema && (
-
-
-
- )}
{canManageEngineCurations && (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx
index ea658c741b8a0..1b353f17855d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx
@@ -5,12 +5,16 @@
* 2.0.
*/
+import { setMockValues } from '../../../../__mocks__/kea_logic';
+
import React from 'react';
import { shallow } from 'enzyme';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { SchemaAddFieldModal } from '../../../../shared/schema';
+
import { EmptyState } from './';
describe('EmptyState', () => {
@@ -24,4 +28,11 @@ describe('EmptyState', () => {
expect.stringContaining('#indexing-documents-guide-schema')
);
});
+
+ it('renders a modal that lets a user add a new schema field', () => {
+ setMockValues({ isModalOpen: true });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(SchemaAddFieldModal)).toHaveLength(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx
index 6d7dd198d5eef..ad9285c7b8fef 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx
@@ -7,14 +7,21 @@
import React from 'react';
-import { EuiPanel, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { useValues, useActions } from 'kea';
+
+import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { SchemaAddFieldModal } from '../../../../shared/schema';
import { DOCS_PREFIX } from '../../../routes';
+import { SchemaLogic } from '../schema_logic';
export const EmptyState: React.FC = () => {
+ const { isModalOpen } = useValues(SchemaLogic);
+ const { addSchemaField, closeModal } = useActions(SchemaLogic);
+
return (
-
+ <>
{
}
/>
-
+ {isModalOpen && (
+
+ )}
+ >
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx
index e76ab60005231..4dd7a869ca27e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx
@@ -14,15 +14,11 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { Loading } from '../../../../shared/loading';
import { SchemaErrorsAccordion } from '../../../../shared/schema';
import { ReindexJob } from './';
describe('ReindexJob', () => {
- const props = {
- schemaBreadcrumb: ['Engines', 'some-engine', 'Schema'],
- };
const values = {
dataLoading: false,
fieldCoercionErrors: {},
@@ -43,27 +39,20 @@ describe('ReindexJob', () => {
});
it('renders', () => {
- const wrapper = shallow( );
+ const wrapper = shallow( );
expect(wrapper.find(SchemaErrorsAccordion)).toHaveLength(1);
expect(wrapper.find(SchemaErrorsAccordion).prop('generateViewPath')).toHaveLength(1);
});
it('calls loadReindexJob on page load', () => {
- shallow( );
+ shallow( );
expect(actions.loadReindexJob).toHaveBeenCalledWith('abc1234567890');
});
- it('renders a loading state', () => {
- setMockValues({ ...values, dataLoading: true });
- const wrapper = shallow( );
-
- expect(wrapper.find(Loading)).toHaveLength(1);
- });
-
it('renders schema errors with links to document pages', () => {
- const wrapper = shallow( );
+ const wrapper = shallow( );
const generateViewPath = wrapper
.find(SchemaErrorsAccordion)
.prop('generateViewPath') as Function;
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx
index 576b4ae11603b..b0a8cbd25f8b0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx
@@ -10,25 +10,17 @@ import { useParams } from 'react-router-dom';
import { useActions, useValues } from 'kea';
-import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FlashMessages } from '../../../../shared/flash_messages';
-import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
-import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
-import { Loading } from '../../../../shared/loading';
import { SchemaErrorsAccordion } from '../../../../shared/schema';
-
import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../../routes';
-import { EngineLogic, generateEnginePath } from '../../engine';
+import { EngineLogic, generateEnginePath, getEngineBreadcrumbs } from '../../engine';
+import { AppSearchPageTemplate } from '../../layout';
+import { SCHEMA_TITLE } from '../constants';
import { ReindexJobLogic } from './reindex_job_logic';
-interface Props {
- schemaBreadcrumb: BreadcrumbTrail;
-}
-
-export const ReindexJob: React.FC = ({ schemaBreadcrumb }) => {
+export const ReindexJob: React.FC = () => {
const { reindexJobId } = useParams() as { reindexJobId: string };
const { loadReindexJob } = useActions(ReindexJobLogic);
const { dataLoading, fieldCoercionErrors } = useValues(ReindexJobLogic);
@@ -40,34 +32,29 @@ export const ReindexJob: React.FC = ({ schemaBreadcrumb }) => {
loadReindexJob(reindexJobId);
}, [reindexJobId]);
- if (dataLoading) return ;
-
return (
- <>
-
-
+
+ generateEnginePath(ENGINE_DOCUMENT_DETAIL_PATH, { documentId })
+ }
/>
-
-
-
- generateEnginePath(ENGINE_DOCUMENT_DETAIL_PATH, { documentId })
- }
- />
-
- >
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts
index 7687296cf9f83..dcc5747b0d32f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts
@@ -140,13 +140,13 @@ describe('SchemaLogic', () => {
describe('selectors', () => {
describe('hasSchema', () => {
- it('returns true when the schema obj has items', () => {
- mountAndSetSchema({ schema: { test: SchemaType.Text } });
+ it('returns true when the cached server schema obj has items', () => {
+ mount({ cachedSchema: { test: SchemaType.Text } });
expect(SchemaLogic.values.hasSchema).toEqual(true);
});
- it('returns false when the schema obj is empty', () => {
- mountAndSetSchema({ schema: {} });
+ it('returns false when the cached server schema obj is empty', () => {
+ mount({ schema: {} });
expect(SchemaLogic.values.hasSchema).toEqual(false);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts
index 3215a46c8e299..3dcafd6782afd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts
@@ -108,7 +108,10 @@ export const SchemaLogic = kea>({
],
},
selectors: {
- hasSchema: [(selectors) => [selectors.schema], (schema) => Object.keys(schema).length > 0],
+ hasSchema: [
+ (selectors) => [selectors.cachedSchema],
+ (cachedSchema) => Object.keys(cachedSchema).length > 0,
+ ],
hasSchemaChanged: [
(selectors) => [selectors.schema, selectors.cachedSchema],
(schema, cachedSchema) => !isEqual(schema, cachedSchema),
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx
index bfa346fee468b..d358c489593c5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx
@@ -10,27 +10,21 @@ import { Route, Switch } from 'react-router-dom';
import { useValues } from 'kea';
-import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { ENGINE_REINDEX_JOB_PATH } from '../../routes';
-import { EngineLogic, getEngineBreadcrumbs } from '../engine';
+import { EngineLogic } from '../engine';
-import { SCHEMA_TITLE } from './constants';
import { ReindexJob } from './reindex_job';
import { Schema, MetaEngineSchema } from './views';
export const SchemaRouter: React.FC = () => {
const { isMetaEngine } = useValues(EngineLogic);
- const schemaBreadcrumb = getEngineBreadcrumbs([SCHEMA_TITLE]);
return (
-
-
-
-
- {isMetaEngine ? : }
+
+ {isMetaEngine ? : }
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx
index 1d677ad08db43..60a0513b774fd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx
@@ -7,6 +7,7 @@
import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic';
import '../../../../__mocks__/shallow_useeffect.mock';
+import '../../../__mocks__/engine_logic.mock';
import React from 'react';
@@ -14,8 +15,6 @@ import { shallow } from 'enzyme';
import { EuiCallOut } from '@elastic/eui';
-import { Loading } from '../../../../shared/loading';
-
import { MetaEnginesSchemaTable, MetaEnginesConflictsTable } from '../components';
import { MetaEngineSchema } from './';
@@ -46,13 +45,6 @@ describe('MetaEngineSchema', () => {
expect(actions.loadSchema).toHaveBeenCalled();
});
- it('renders a loading state', () => {
- setMockValues({ ...values, dataLoading: true });
- const wrapper = shallow( );
-
- expect(wrapper.find(Loading)).toHaveLength(1);
- });
-
it('renders an inactive fields callout & table when source engines have schema conflicts', () => {
setMockValues({ ...values, hasConflicts: true, conflictingFieldsCount: 5 });
const wrapper = shallow( );
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx
index 4c0235cf81129..2eb8bac00a040 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx
@@ -9,14 +9,15 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
-import { EuiPageHeader, EuiPageContentBody, EuiCallOut, EuiSpacer } from '@elastic/eui';
+import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FlashMessages } from '../../../../shared/flash_messages';
-import { Loading } from '../../../../shared/loading';
import { DataPanel } from '../../data_panel';
+import { getEngineBreadcrumbs } from '../../engine';
+import { AppSearchPageTemplate } from '../../layout';
import { MetaEnginesSchemaTable, MetaEnginesConflictsTable } from '../components';
+import { SCHEMA_TITLE } from '../constants';
import { MetaEngineSchemaLogic } from '../schema_meta_engine_logic';
export const MetaEngineSchema: React.FC = () => {
@@ -27,90 +28,88 @@ export const MetaEngineSchema: React.FC = () => {
loadSchema();
}, []);
- if (dataLoading) return ;
-
return (
- <>
-
-
-
- {hasConflicts && (
- <>
-
+ {hasConflicts && (
+ <>
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription',
{
defaultMessage:
- '{conflictingFieldsCount, plural, one {# field is} other {# fields are}} not searchable',
- values: { conflictingFieldsCount },
+ 'The field(s) have an inconsistent field-type across the source engines that make up this meta engine. Apply a consistent field-type from the source engines to make these fields searchable.',
}
)}
- >
-
- {i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription',
- {
- defaultMessage:
- 'The field(s) have an inconsistent field-type across the source engines that make up this meta engine. Apply a consistent field-type from the source engines to make these fields searchable.',
- }
- )}
-
-
-
- >
+
+
+
+ >
+ )}
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle',
+ { defaultMessage: 'Active fields' }
+ )}
+
+ }
+ subtitle={i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription',
+ { defaultMessage: 'Fields which belong to one or more engine.' }
)}
+ >
+
+
+
+ {hasConflicts && (
{i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle',
- { defaultMessage: 'Active fields' }
+ 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle',
+ { defaultMessage: 'Inactive fields' }
)}
}
subtitle={i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription',
- { defaultMessage: 'Fields which belong to one or more engine.' }
+ 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription',
+ {
+ defaultMessage:
+ 'These fields have type conflicts. To activate these fields, change types in the source engines to match.',
+ }
)}
>
-
+
-
- {hasConflicts && (
-
- {i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle',
- { defaultMessage: 'Inactive fields' }
- )}
-
- }
- subtitle={i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription',
- {
- defaultMessage:
- 'These fields have type conflicts. To activate these fields, change types in the source engines to match.',
- }
- )}
- >
-
-
- )}
-
- >
+ )}
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx
index 91ec8eda55fc3..cae16d70592fa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx
@@ -7,17 +7,18 @@
import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic';
import '../../../../__mocks__/shallow_useeffect.mock';
+import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow } from 'enzyme';
-import { EuiPageHeader, EuiButton } from '@elastic/eui';
+import { EuiButton } from '@elastic/eui';
-import { Loading } from '../../../../shared/loading';
import { SchemaAddFieldModal } from '../../../../shared/schema';
+import { getPageHeaderActions } from '../../../../test_helpers';
-import { SchemaCallouts, SchemaTable, EmptyState } from '../components';
+import { SchemaCallouts, SchemaTable } from '../components';
import { Schema } from './';
@@ -56,27 +57,8 @@ describe('Schema', () => {
expect(actions.loadSchema).toHaveBeenCalled();
});
- it('renders a loading state', () => {
- setMockValues({ ...values, dataLoading: true });
- const wrapper = shallow( );
-
- expect(wrapper.find(Loading)).toHaveLength(1);
- });
-
- it('renders an empty state', () => {
- setMockValues({ ...values, hasSchema: false });
- const wrapper = shallow( );
-
- expect(wrapper.find(EmptyState)).toHaveLength(1);
- });
-
describe('page action buttons', () => {
- const subject = () =>
- shallow( )
- .find(EuiPageHeader)
- .dive()
- .children()
- .dive();
+ const subject = () => getPageHeaderActions(shallow( ));
it('renders', () => {
const wrapper = subject();
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx
index 7bc995b16468a..d2a760e8accff 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx
@@ -9,14 +9,15 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
-import { EuiPageHeader, EuiButton, EuiPageContentBody } from '@elastic/eui';
+import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FlashMessages } from '../../../../shared/flash_messages';
-import { Loading } from '../../../../shared/loading';
import { SchemaAddFieldModal } from '../../../../shared/schema';
+import { getEngineBreadcrumbs } from '../../engine';
+import { AppSearchPageTemplate } from '../../layout';
import { SchemaCallouts, SchemaTable, EmptyState } from '../components';
+import { SCHEMA_TITLE } from '../constants';
import { SchemaLogic } from '../schema_logic';
export const Schema: React.FC = () => {
@@ -31,19 +32,18 @@ export const Schema: React.FC = () => {
loadSchema();
}, []);
- if (dataLoading) return ;
-
return (
- <>
- {
>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel',
- { defaultMessage: 'Update types' }
+ { defaultMessage: 'Save changes' }
)}
,
{
{ defaultMessage: 'Create a schema field' }
)}
,
- ]}
- />
-
-
-
- {hasSchema ? : }
- {isModalOpen && (
-
- )}
-
- >
+ ],
+ }}
+ isLoading={dataLoading}
+ isEmptyState={!hasSchema}
+ emptyState={ }
+ >
+
+
+ {isModalOpen && (
+
+ )}
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/add_field_modal/index.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/add_field_modal/index.tsx
index 902417d02665e..ba9da900c0145 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/add_field_modal/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/add_field_modal/index.tsx
@@ -10,6 +10,7 @@ import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import {
EuiButton,
EuiButtonEmpty,
+ EuiCallOut,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
@@ -83,8 +84,13 @@ export const SchemaAddFieldModal: React.FC = ({
{ADD_FIELD_MODAL_TITLE}
- {ADD_FIELD_MODAL_DESCRIPTION}
-
+ {ADD_FIELD_MODAL_DESCRIPTION}}
+ />
+
From dec77cfafb0cd557eaf7a6d2ab72280b9801fb6d Mon Sep 17 00:00:00 2001
From: Chris Roberson
Date: Tue, 22 Jun 2021 16:01:43 -0400
Subject: [PATCH 08/78] [Alerting] Add event log entry when an action starts
executing (#102370)
* First steps for adding action execution to event log
* Fix tests
* Move the event to the actions plugin
* Update functional tests
* Fix tests
* Fix types
---
.../actions/server/constants/event_log.ts | 1 +
.../server/lib/action_executor.test.ts | 47 ++++++++++-
.../actions/server/lib/action_executor.ts | 12 +++
.../tests/actions/execute.ts | 78 +++++++++++++++----
.../spaces_only/tests/actions/execute.ts | 50 ++++++++----
5 files changed, 156 insertions(+), 32 deletions(-)
diff --git a/x-pack/plugins/actions/server/constants/event_log.ts b/x-pack/plugins/actions/server/constants/event_log.ts
index 508709c8783ab..9163a0d105ce8 100644
--- a/x-pack/plugins/actions/server/constants/event_log.ts
+++ b/x-pack/plugins/actions/server/constants/event_log.ts
@@ -8,5 +8,6 @@
export const EVENT_LOG_PROVIDER = 'actions';
export const EVENT_LOG_ACTIONS = {
execute: 'execute',
+ executeStart: 'execute-start',
executeViaHttp: 'execute-via-http',
};
diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts
index 8ec94c4d4a552..37d461d6b2a50 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.test.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts
@@ -23,6 +23,7 @@ const services = actionsMock.createServices();
const actionsClient = actionsClientMock.create();
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient();
const actionTypeRegistry = actionTypeRegistryMock.create();
+const eventLogger = eventLoggerMock.create();
const executeParams = {
actionId: '1',
@@ -42,7 +43,7 @@ actionExecutor.initialize({
getActionsClientWithRequest,
actionTypeRegistry,
encryptedSavedObjectsClient,
- eventLogger: eventLoggerMock.create(),
+ eventLogger,
preconfiguredActions: [],
});
@@ -379,6 +380,50 @@ test('logs a warning when alert executor returns invalid status', async () => {
);
});
+test('writes to event log for execute and execute start', async () => {
+ const executorMock = setupActionExecutorMock();
+ executorMock.mockResolvedValue({
+ actionId: '1',
+ status: 'ok',
+ });
+ await actionExecutor.execute(executeParams);
+ expect(eventLogger.logEvent).toHaveBeenCalledTimes(2);
+ expect(eventLogger.logEvent.mock.calls[0][0]).toMatchObject({
+ event: {
+ action: 'execute-start',
+ },
+ kibana: {
+ saved_objects: [
+ {
+ rel: 'primary',
+ type: 'action',
+ id: '1',
+ type_id: 'test',
+ namespace: 'some-namespace',
+ },
+ ],
+ },
+ message: 'action started: test:1: action-1',
+ });
+ expect(eventLogger.logEvent.mock.calls[1][0]).toMatchObject({
+ event: {
+ action: 'execute',
+ },
+ kibana: {
+ saved_objects: [
+ {
+ rel: 'primary',
+ type: 'action',
+ id: '1',
+ type_id: 'test',
+ namespace: 'some-namespace',
+ },
+ ],
+ },
+ message: 'action executed: test:1: action-1',
+ });
+});
+
function setupActionExecutorMock() {
const actionType: jest.Mocked = {
id: 'test',
diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts
index 9d2b937734fb0..e9e7b17288611 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.ts
@@ -7,6 +7,7 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { Logger, KibanaRequest } from 'src/core/server';
+import { cloneDeep } from 'lodash';
import { withSpan } from '@kbn/apm-utils';
import { validateParams, validateConfig, validateSecrets } from './validate_with_schema';
import {
@@ -168,6 +169,17 @@ export class ActionExecutor {
}
eventLogger.startTiming(event);
+
+ const startEvent = cloneDeep({
+ ...event,
+ event: {
+ ...event.event,
+ action: EVENT_LOG_ACTIONS.executeStart,
+ },
+ message: `action started: ${actionLabel}`,
+ });
+ eventLogger.logEvent(startEvent);
+
let rawResult: ActionTypeExecutorResult;
try {
rawResult = await actionType.executor({
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
index f7d7c1df8fd46..5c578d2d08dae 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
@@ -519,47 +519,93 @@ export default function ({ getService }: FtrProviderContext) {
type: 'action',
id: connectorId,
provider: 'actions',
- actions: new Map([['execute', { equal: 1 }]]),
- filter: 'event.action:(execute)',
+ actions: new Map([
+ ['execute-start', { equal: 1 }],
+ ['execute', { equal: 1 }],
+ ]),
+ // filter: 'event.action:(execute)',
});
});
- const event = events[0];
+ const startExecuteEvent = events[0];
+ const executeEvent = events[1];
- const duration = event?.event?.duration;
- const eventStart = Date.parse(event?.event?.start || 'undefined');
- const eventEnd = Date.parse(event?.event?.end || 'undefined');
+ const duration = executeEvent?.event?.duration;
+ const executeEventStart = Date.parse(executeEvent?.event?.start || 'undefined');
+ const startExecuteEventStart = Date.parse(startExecuteEvent?.event?.start || 'undefined');
+ const executeEventEnd = Date.parse(executeEvent?.event?.end || 'undefined');
const dateNow = Date.now();
expect(typeof duration).to.be('number');
- expect(eventStart).to.be.ok();
- expect(eventEnd).to.be.ok();
+ expect(executeEventStart).to.be.ok();
+ expect(startExecuteEventStart).to.equal(executeEventStart);
+ expect(executeEventEnd).to.be.ok();
const durationDiff = Math.abs(
- Math.round(duration! / NANOS_IN_MILLIS) - (eventEnd - eventStart)
+ Math.round(duration! / NANOS_IN_MILLIS) - (executeEventEnd - executeEventStart)
);
// account for rounding errors
expect(durationDiff < 1).to.equal(true);
- expect(eventStart <= eventEnd).to.equal(true);
- expect(eventEnd <= dateNow).to.equal(true);
+ expect(executeEventStart <= executeEventEnd).to.equal(true);
+ expect(executeEventEnd <= dateNow).to.equal(true);
- expect(event?.event?.outcome).to.equal(outcome);
+ expect(executeEvent?.event?.outcome).to.equal(outcome);
- expect(event?.kibana?.saved_objects).to.eql([
+ expect(executeEvent?.kibana?.saved_objects).to.eql([
{
rel: 'primary',
type: 'action',
id: connectorId,
+ namespace: 'space1',
type_id: actionTypeId,
- namespace: spaceId,
},
]);
+ expect(startExecuteEvent?.kibana?.saved_objects).to.eql(executeEvent?.kibana?.saved_objects);
- expect(event?.message).to.eql(message);
+ expect(executeEvent?.message).to.eql(message);
+ expect(startExecuteEvent?.message).to.eql(message.replace('executed', 'started'));
if (errorMessage) {
- expect(event?.error?.message).to.eql(errorMessage);
+ expect(executeEvent?.error?.message).to.eql(errorMessage);
}
+
+ // const event = events[0];
+
+ // const duration = event?.event?.duration;
+ // const eventStart = Date.parse(event?.event?.start || 'undefined');
+ // const eventEnd = Date.parse(event?.event?.end || 'undefined');
+ // const dateNow = Date.now();
+
+ // expect(typeof duration).to.be('number');
+ // expect(eventStart).to.be.ok();
+ // expect(eventEnd).to.be.ok();
+
+ // const durationDiff = Math.abs(
+ // Math.round(duration! / NANOS_IN_MILLIS) - (eventEnd - eventStart)
+ // );
+
+ // // account for rounding errors
+ // expect(durationDiff < 1).to.equal(true);
+ // expect(eventStart <= eventEnd).to.equal(true);
+ // expect(eventEnd <= dateNow).to.equal(true);
+
+ // expect(event?.event?.outcome).to.equal(outcome);
+
+ // expect(event?.kibana?.saved_objects).to.eql([
+ // {
+ // rel: 'primary',
+ // type: 'action',
+ // id: connectorId,
+ // type_id: actionTypeId,
+ // namespace: spaceId,
+ // },
+ // ]);
+
+ // expect(event?.message).to.eql(message);
+
+ // if (errorMessage) {
+ // expect(event?.error?.message).to.eql(errorMessage);
+ // }
}
}
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts
index 147b6abfb88d1..d494c99c80e8f 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts
@@ -100,6 +100,7 @@ export default function ({ getService }: FtrProviderContext) {
actionTypeId: 'test.index-record',
outcome: 'success',
message: `action executed: test.index-record:${createdAction.id}: My action`,
+ startMessage: `action started: test.index-record:${createdAction.id}: My action`,
});
});
@@ -336,10 +337,19 @@ export default function ({ getService }: FtrProviderContext) {
outcome: string;
message: string;
errorMessage?: string;
+ startMessage?: string;
}
async function validateEventLog(params: ValidateEventLogParams): Promise {
- const { spaceId, actionId, actionTypeId, outcome, message, errorMessage } = params;
+ const {
+ spaceId,
+ actionId,
+ actionTypeId,
+ outcome,
+ message,
+ startMessage,
+ errorMessage,
+ } = params;
const events: IValidatedEvent[] = await retry.try(async () => {
return await getEventLog({
@@ -348,33 +358,39 @@ export default function ({ getService }: FtrProviderContext) {
type: 'action',
id: actionId,
provider: 'actions',
- actions: new Map([['execute', { equal: 1 }]]),
+ actions: new Map([
+ ['execute-start', { equal: 1 }],
+ ['execute', { equal: 1 }],
+ ]),
});
});
- const event = events[0];
+ const startExecuteEvent = events[0];
+ const executeEvent = events[1];
- const duration = event?.event?.duration;
- const eventStart = Date.parse(event?.event?.start || 'undefined');
- const eventEnd = Date.parse(event?.event?.end || 'undefined');
+ const duration = executeEvent?.event?.duration;
+ const executeEventStart = Date.parse(executeEvent?.event?.start || 'undefined');
+ const startExecuteEventStart = Date.parse(startExecuteEvent?.event?.start || 'undefined');
+ const executeEventEnd = Date.parse(executeEvent?.event?.end || 'undefined');
const dateNow = Date.now();
expect(typeof duration).to.be('number');
- expect(eventStart).to.be.ok();
- expect(eventEnd).to.be.ok();
+ expect(executeEventStart).to.be.ok();
+ expect(startExecuteEventStart).to.equal(executeEventStart);
+ expect(executeEventEnd).to.be.ok();
const durationDiff = Math.abs(
- Math.round(duration! / NANOS_IN_MILLIS) - (eventEnd - eventStart)
+ Math.round(duration! / NANOS_IN_MILLIS) - (executeEventEnd - executeEventStart)
);
// account for rounding errors
expect(durationDiff < 1).to.equal(true);
- expect(eventStart <= eventEnd).to.equal(true);
- expect(eventEnd <= dateNow).to.equal(true);
+ expect(executeEventStart <= executeEventEnd).to.equal(true);
+ expect(executeEventEnd <= dateNow).to.equal(true);
- expect(event?.event?.outcome).to.equal(outcome);
+ expect(executeEvent?.event?.outcome).to.equal(outcome);
- expect(event?.kibana?.saved_objects).to.eql([
+ expect(executeEvent?.kibana?.saved_objects).to.eql([
{
rel: 'primary',
type: 'action',
@@ -383,11 +399,15 @@ export default function ({ getService }: FtrProviderContext) {
type_id: actionTypeId,
},
]);
+ expect(startExecuteEvent?.kibana?.saved_objects).to.eql(executeEvent?.kibana?.saved_objects);
- expect(event?.message).to.eql(message);
+ expect(executeEvent?.message).to.eql(message);
+ if (startMessage) {
+ expect(startExecuteEvent?.message).to.eql(startMessage);
+ }
if (errorMessage) {
- expect(event?.error?.message).to.eql(errorMessage);
+ expect(executeEvent?.error?.message).to.eql(errorMessage);
}
}
}
From b161bf03be07d6bc9fe688c03be8909fc26bae5f Mon Sep 17 00:00:00 2001
From: Melissa Alvarez
Date: Tue, 22 Jun 2021 16:58:18 -0400
Subject: [PATCH 09/78] [ML] Anomaly Detection: Visualize delayed - data Part 2
(#102270)
* add link in datafeed tab.remove interval
* add annotation overlay to chart
* adds annotations checkbox
* ensure annotation with same start/end time show up in chart
* update annotations time format
* move time format to client
* adds info tooltip to modal title
* adds model snapshots to datafeed chart
---
x-pack/plugins/ml/common/types/results.ts | 4 +
.../annotations_table/annotations_table.js | 10 +-
.../components/datafeed_modal/constants.ts | 2 +-
.../datafeed_modal/datafeed_modal.tsx | 213 ++++++++++++++----
.../datafeed_modal/get_interval_options.ts | 118 ----------
.../components/job_details/job_details.js | 76 +++++--
.../job_details/job_details_pane.js | 13 +-
.../services/ml_api_service/results.ts | 7 +-
.../models/results_service/results_service.ts | 66 +++++-
9 files changed, 308 insertions(+), 201 deletions(-)
delete mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/get_interval_options.ts
diff --git a/x-pack/plugins/ml/common/types/results.ts b/x-pack/plugins/ml/common/types/results.ts
index fa40cefcaed48..74d3286438588 100644
--- a/x-pack/plugins/ml/common/types/results.ts
+++ b/x-pack/plugins/ml/common/types/results.ts
@@ -6,6 +6,7 @@
*/
import { estypes } from '@elastic/elasticsearch';
+import { LineAnnotationDatum, RectAnnotationDatum } from '@elastic/charts';
export interface GetStoppedPartitionResult {
jobs: string[] | Record;
@@ -13,6 +14,9 @@ export interface GetStoppedPartitionResult {
export interface GetDatafeedResultsChartDataResult {
bucketResults: number[][];
datafeedResults: number[][];
+ annotationResultsRect: RectAnnotationDatum[];
+ annotationResultsLine: LineAnnotationDatum[];
+ modelSnapshotResultsLine: LineAnnotationDatum[];
}
export interface DatafeedResultsChartDataParams {
diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
index afed7e79ff757..b68e64a5d9f6a 100644
--- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
+++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
@@ -494,13 +494,13 @@ class AnnotationsTableUI extends Component {
render: (annotation) => {
const viewDataFeedText = (
);
const viewDataFeedTooltipAriaLabelText = i18n.translate(
- 'xpack.ml.annotationsTable.viewDatafeedTooltipAriaLabel',
- { defaultMessage: 'View datafeed' }
+ 'xpack.ml.annotationsTable.datafeedChartTooltipAriaLabel',
+ { defaultMessage: 'Datafeed chart' }
);
return (
) : null}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts
index 71f3795518bc9..b3b9487523196 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts
@@ -15,7 +15,7 @@ export const CHART_DIRECTION = {
export type ChartDirectionType = typeof CHART_DIRECTION[keyof typeof CHART_DIRECTION];
// [width, height]
-export const CHART_SIZE: ChartSizeArray = ['100%', 300];
+export const CHART_SIZE: ChartSizeArray = ['100%', 380];
export const TAB_IDS = {
CHART: 'chart',
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx
index cf547a49cac4c..2dece82e6f5c7 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx
@@ -11,25 +11,35 @@ import { i18n } from '@kbn/i18n';
import moment from 'moment';
import {
EuiButtonEmpty,
+ EuiCheckbox,
EuiDatePicker,
EuiFlexGroup,
EuiFlexItem,
+ EuiIcon,
+ EuiIconTip,
EuiLoadingChart,
EuiModal,
EuiModalHeader,
EuiModalBody,
- EuiSelect,
EuiSpacer,
EuiTabs,
EuiTab,
+ EuiText,
+ EuiTitle,
EuiToolTip,
+ htmlIdGenerator,
} from '@elastic/eui';
import {
+ AnnotationDomainType,
Axis,
Chart,
CurveType,
+ LineAnnotation,
LineSeries,
+ LineAnnotationDatum,
Position,
+ RectAnnotation,
+ RectAnnotationDatum,
ScaleType,
Settings,
timeFormatter,
@@ -42,7 +52,6 @@ import { useMlApiContext } from '../../../../contexts/kibana';
import { useCurrentEuiTheme } from '../../../../components/color_range_legend';
import { JobMessagesPane } from '../job_details/job_messages_pane';
import { EditQueryDelay } from './edit_query_delay';
-import { getIntervalOptions } from './get_interval_options';
import {
CHART_DIRECTION,
ChartDirectionType,
@@ -53,12 +62,18 @@ import {
} from './constants';
import { loadFullJob } from '../utils';
-const dateFormatter = timeFormatter('MM-DD HH:mm');
+const dateFormatter = timeFormatter('MM-DD HH:mm:ss');
+const MAX_CHART_POINTS = 480;
interface DatafeedModalProps {
jobId: string;
end: number;
- onClose: (deletionApproved?: boolean) => void;
+ onClose: () => void;
+}
+
+function setLineAnnotationHeader(lineDatum: LineAnnotationDatum) {
+ lineDatum.header = dateFormatter(lineDatum.dataValue);
+ return lineDatum;
}
export const DatafeedModal: FC = ({ jobId, end, onClose }) => {
@@ -68,11 +83,17 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
isInitialized: boolean;
}>({ datafeedConfig: undefined, bucketSpan: undefined, isInitialized: false });
const [endDate, setEndDate] = useState(moment(end));
- const [interval, setInterval] = useState();
const [selectedTabId, setSelectedTabId] = useState(TAB_IDS.CHART);
const [isLoadingChartData, setIsLoadingChartData] = useState(false);
const [bucketData, setBucketData] = useState([]);
+ const [annotationData, setAnnotationData] = useState<{
+ rect: RectAnnotationDatum[];
+ line: LineAnnotationDatum[];
+ }>({ rect: [], line: [] });
+ const [modelSnapshotData, setModelSnapshotData] = useState([]);
const [sourceData, setSourceData] = useState([]);
+ const [showAnnotations, setShowAnnotations] = useState(true);
+ const [showModelSnapshots, setShowModelSnapshots] = useState(true);
const {
results: { getDatafeedResultChartData },
@@ -102,25 +123,30 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
const handleChange = (date: moment.Moment) => setEndDate(date);
const handleEndDateChange = (direction: ChartDirectionType) => {
- if (interval === undefined) return;
+ if (data.bucketSpan === undefined) return;
const newEndDate = endDate.clone();
- const [count, type] = interval.split(' ');
+ const unitMatch = data.bucketSpan.match(/[d | h| m | s]/g)!;
+ const unit = unitMatch[0];
+ const count = Number(data.bucketSpan.replace(/[^0-9]/g, ''));
if (direction === CHART_DIRECTION.FORWARD) {
- newEndDate.add(Number(count), type);
+ newEndDate.add(MAX_CHART_POINTS * count, unit);
} else {
- newEndDate.subtract(Number(count), type);
+ newEndDate.subtract(MAX_CHART_POINTS * count, unit);
}
setEndDate(newEndDate);
};
const getChartData = useCallback(async () => {
- if (interval === undefined) return;
+ if (data.bucketSpan === undefined) return;
const endTimestamp = moment(endDate).valueOf();
- const [count, type] = interval.split(' ');
- const startMoment = endDate.clone().subtract(Number(count), type);
+ const unitMatch = data.bucketSpan.match(/[d | h| m | s]/g)!;
+ const unit = unitMatch[0];
+ const count = Number(data.bucketSpan.replace(/[^0-9]/g, ''));
+ // STARTTIME = ENDTIME - (BucketSpan * MAX_CHART_POINTS)
+ const startMoment = endDate.clone().subtract(MAX_CHART_POINTS * count, unit);
const startTimestamp = moment(startMoment).valueOf();
try {
@@ -128,6 +154,11 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
setSourceData(chartData.datafeedResults);
setBucketData(chartData.bucketResults);
+ setAnnotationData({
+ rect: chartData.annotationResultsRect,
+ line: chartData.annotationResultsLine.map(setLineAnnotationHeader),
+ });
+ setModelSnapshotData(chartData.modelSnapshotResultsLine.map(setLineAnnotationHeader));
} catch (error) {
const title = i18n.translate('xpack.ml.jobsList.datafeedModal.errorToastTitle', {
defaultMessage: 'Error fetching data',
@@ -135,7 +166,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
displayErrorToast(error, title);
}
setIsLoadingChartData(false);
- }, [endDate, interval]);
+ }, [endDate, data.bucketSpan]);
const getJobData = async () => {
try {
@@ -145,11 +176,6 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
bucketSpan: job.analysis_config.bucket_span,
isInitialized: true,
});
- const intervalOptions = getIntervalOptions(job.analysis_config.bucket_span);
- const initialInterval = intervalOptions.length
- ? intervalOptions[intervalOptions.length - 1]
- : undefined;
- setInterval(initialInterval?.value || '72 hours');
} catch (error) {
displayErrorToast(error);
}
@@ -161,20 +187,17 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
useEffect(
function loadChartData() {
- if (interval !== undefined) {
+ if (data.bucketSpan !== undefined) {
setIsLoadingChartData(true);
getChartData();
}
},
- [endDate, interval]
+ [endDate, data.bucketSpan]
);
const { datafeedConfig, bucketSpan, isInitialized } = data;
-
- const intervalOptions = useMemo(() => {
- if (bucketSpan === undefined) return [];
- return getIntervalOptions(bucketSpan);
- }, [bucketSpan]);
+ const checkboxIdAnnotation = useMemo(() => htmlIdGenerator()(), []);
+ const checkboxIdModelSnapshot = useMemo(() => htmlIdGenerator()(), []);
return (
= ({ jobId, end, onClose }) =
-
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
= ({ jobId, end, onClose }) =
-
- setInterval(e.target.value)}
- aria-label={i18n.translate(
- 'xpack.ml.jobsList.datafeedModal.intervalSelection',
- {
- defaultMessage: 'Datafeed modal chart interval selection',
- }
- )}
- />
-
= ({ jobId, end, onClose }) =
isEnabled={datafeedConfig.state === DATAFEED_STATE.STOPPED}
/>
+
+
+
+
+
+
+ }
+ checked={showAnnotations}
+ onChange={() => setShowAnnotations(!showAnnotations)}
+ />
+
+
+
+
+
+ }
+ checked={showModelSnapshots}
+ onChange={() => setShowModelSnapshots(!showModelSnapshots)}
+ />
+
+
+
@@ -298,7 +362,65 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) =
})}
position={Position.Left}
/>
+ {showModelSnapshots ? (
+ }
+ markerPosition={Position.Top}
+ style={{
+ line: {
+ strokeWidth: 3,
+ stroke: euiTheme.euiColorVis1,
+ opacity: 0.5,
+ },
+ }}
+ />
+ ) : null}
+ {showAnnotations ? (
+ <>
+ }
+ markerPosition={Position.Top}
+ style={{
+ line: {
+ strokeWidth: 3,
+ stroke: euiTheme.euiColorDangerText,
+ opacity: 0.5,
+ },
+ }}
+ />
+
+ >
+ ) : null}
= ({ jobId, end, onClose }) =
curve={CurveType.LINEAR}
/>
{
- const unitMatch = bucketSpan.match(/[d | h| m | s]/g)!;
- const unit = unitMatch[0];
- const count = Number(bucketSpan.replace(/[^0-9]/g, ''));
-
- const intervalOptions = [];
-
- if (['s', 'ms', 'micros', 'nanos'].includes(unit)) {
- intervalOptions.push(
- {
- value: '1 hour',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.1hourOption', {
- defaultMessage: '{count} hour',
- values: { count: 1 },
- }),
- },
- {
- value: '2 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.2hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 2 },
- }),
- }
- );
- }
-
- if ((unit === 'm' && count <= 4) || unit === 'h') {
- intervalOptions.push(
- {
- value: '3 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.3hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 3 },
- }),
- },
- {
- value: '8 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.8hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 8 },
- }),
- },
- {
- value: '12 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.12hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 12 },
- }),
- },
- {
- value: '24 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.24hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 24 },
- }),
- }
- );
- }
-
- if ((unit === 'm' && count >= 5 && count <= 15) || unit === 'h') {
- intervalOptions.push(
- {
- value: '48 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.48hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 48 },
- }),
- },
- {
- value: '72 hours',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.72hourOption', {
- defaultMessage: '{count} hours',
- values: { count: 72 },
- }),
- }
- );
- }
-
- if ((unit === 'm' && count >= 10 && count <= 15) || unit === 'h' || unit === 'd') {
- intervalOptions.push(
- {
- value: '5 days',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.5daysOption', {
- defaultMessage: '{count} days',
- values: { count: 5 },
- }),
- },
- {
- value: '7 days',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.7daysOption', {
- defaultMessage: '{count} days',
- values: { count: 7 },
- }),
- }
- );
- }
-
- if (unit === 'h' || unit === 'd') {
- intervalOptions.push({
- value: '14 days',
- text: i18n.translate('xpack.ml.jobsList.datafeedModal.14DaysOption', {
- defaultMessage: '{count} days',
- values: { count: 14 },
- }),
- });
- }
-
- return intervalOptions;
-};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
index b514c8433daf4..d3856e6afa398 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
@@ -7,26 +7,29 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
-
-import { EuiTabbedContent, EuiLoadingSpinner } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { EuiButtonIcon, EuiTabbedContent, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import { extractJobDetails } from './extract_job_details';
import { JsonPane } from './json_tab';
import { DatafeedPreviewPane } from './datafeed_preview_tab';
import { AnnotationsTable } from '../../../../components/annotations/annotations_table';
+import { DatafeedModal } from '../datafeed_modal';
import { AnnotationFlyout } from '../../../../components/annotations/annotation_flyout';
import { ModelSnapshotTable } from '../../../../components/model_snapshots';
import { ForecastsTable } from './forecasts_table';
import { JobDetailsPane } from './job_details_pane';
import { JobMessagesPane } from './job_messages_pane';
-import { i18n } from '@kbn/i18n';
import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
export class JobDetailsUI extends Component {
constructor(props) {
super(props);
- this.state = {};
+ this.state = {
+ datafeedModalVisible: false,
+ };
if (this.props.addYourself) {
this.props.addYourself(props.jobId, (j) => this.updateJob(j));
}
@@ -77,6 +80,30 @@ export class JobDetailsUI extends Component {
alertRules,
} = extractJobDetails(job, basePath, refreshJobList);
+ datafeed.titleAction = (
+
+ }
+ >
+
+ this.setState({
+ datafeedModalVisible: true,
+ })
+ }
+ />
+
+ );
+
const tabs = [
{
id: 'job-settings',
@@ -105,6 +132,32 @@ export class JobDetailsUI extends Component {
/>
),
},
+ {
+ id: 'datafeed',
+ 'data-test-subj': 'mlJobListTab-datafeed',
+ name: i18n.translate('xpack.ml.jobsList.jobDetails.tabs.datafeedLabel', {
+ defaultMessage: 'Datafeed',
+ }),
+ content: (
+ <>
+
+ {this.props.jobId && this.state.datafeedModalVisible ? (
+ {
+ this.setState({
+ datafeedModalVisible: false,
+ });
+ }}
+ end={job.data_counts.latest_bucket_timestamp}
+ jobId={this.props.jobId}
+ />
+ ) : null}
+ >
+ ),
+ },
{
id: 'counts',
'data-test-subj': 'mlJobListTab-counts',
@@ -137,21 +190,6 @@ export class JobDetailsUI extends Component {
];
if (showFullDetails && datafeed.items.length) {
- // Datafeed should be at index 2 in tabs array for full details
- tabs.splice(2, 0, {
- id: 'datafeed',
- 'data-test-subj': 'mlJobListTab-datafeed',
- name: i18n.translate('xpack.ml.jobsList.jobDetails.tabs.datafeedLabel', {
- defaultMessage: 'Datafeed',
- }),
- content: (
-
- ),
- });
-
tabs.push(
{
id: 'datafeed-preview',
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js
index 49d9bcde49052..4046f4d5d8071 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js
@@ -9,6 +9,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
+ EuiFlexGroup,
+ EuiFlexItem,
EuiTitle,
EuiTable,
EuiTableBody,
@@ -42,9 +44,14 @@ function Section({ section }) {
return (
-
- {section.title}
-
+
+
+
+ {section.title}
+
+
+ {section.titleAction}
+
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
index 19ba5aa304bf0..25ef36782207f 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
@@ -6,7 +6,10 @@
*/
// Service for obtaining data for the ML Results dashboards.
-import { GetStoppedPartitionResult } from '../../../../common/types/results';
+import {
+ GetStoppedPartitionResult,
+ GetDatafeedResultsChartDataResult,
+} from '../../../../common/types/results';
import { HttpService } from '../http_service';
import { basePath } from './index';
import { JobId } from '../../../../common/types/anomaly_detection_jobs';
@@ -148,7 +151,7 @@ export const resultsApiProvider = (httpService: HttpService) => ({
start,
end,
});
- return httpService.http({
+ return httpService.http({
path: `${basePath()}/results/datafeed_results_chart`,
method: 'POST',
body,
diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts
index 9413ee00184d2..81ee394b99704 100644
--- a/x-pack/plugins/ml/server/models/results_service/results_service.ts
+++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts
@@ -27,6 +27,7 @@ import {
import { MlJobsResponse } from '../../../common/types/job_service';
import type { MlClient } from '../../lib/ml_client';
import { datafeedsProvider } from '../job_service/datafeeds';
+import { annotationServiceProvider } from '../annotation_service';
// Service for carrying out Elasticsearch queries to obtain data for the
// ML Results dashboards.
@@ -620,13 +621,19 @@ export function resultsServiceProvider(mlClient: MlClient, client?: IScopedClust
const finalResults: GetDatafeedResultsChartDataResult = {
bucketResults: [],
datafeedResults: [],
+ annotationResultsRect: [],
+ annotationResultsLine: [],
+ modelSnapshotResultsLine: [],
};
const { getDatafeedByJobId } = datafeedsProvider(client!, mlClient);
- const datafeedConfig = await getDatafeedByJobId(jobId);
- const { body: jobsResponse } = await mlClient.getJobs({ job_id: jobId });
- if (jobsResponse.count === 0 || jobsResponse.jobs === undefined) {
+ const [datafeedConfig, { body: jobsResponse }] = await Promise.all([
+ getDatafeedByJobId(jobId),
+ mlClient.getJobs({ job_id: jobId }),
+ ]);
+
+ if (jobsResponse && (jobsResponse.count === 0 || jobsResponse.jobs === undefined)) {
throw Boom.notFound(`Job with the id "${jobId}" not found`);
}
@@ -696,10 +703,25 @@ export function resultsServiceProvider(mlClient: MlClient, client?: IScopedClust
]) || [];
}
- const bucketResp = await mlClient.getBuckets({
- job_id: jobId,
- body: { desc: true, start: String(start), end: String(end), page: { from: 0, size: 1000 } },
- });
+ const { getAnnotations } = annotationServiceProvider(client!);
+
+ const [bucketResp, annotationResp, { body: modelSnapshotsResp }] = await Promise.all([
+ mlClient.getBuckets({
+ job_id: jobId,
+ body: { desc: true, start: String(start), end: String(end), page: { from: 0, size: 1000 } },
+ }),
+ getAnnotations({
+ jobIds: [jobId],
+ earliestMs: start,
+ latestMs: end,
+ maxAnnotations: 1000,
+ }),
+ mlClient.getModelSnapshots({
+ job_id: jobId,
+ start: String(start),
+ end: String(end),
+ }),
+ ]);
const bucketResults = bucketResp?.body?.buckets ?? [];
bucketResults.forEach((dataForTime) => {
@@ -708,6 +730,36 @@ export function resultsServiceProvider(mlClient: MlClient, client?: IScopedClust
finalResults.bucketResults.push([timestamp, eventCount]);
});
+ const annotationResults = annotationResp.annotations[jobId] || [];
+ annotationResults.forEach((annotation) => {
+ const timestamp = Number(annotation?.timestamp);
+ const endTimestamp = Number(annotation?.end_timestamp);
+ if (timestamp === endTimestamp) {
+ finalResults.annotationResultsLine.push({
+ dataValue: timestamp,
+ details: annotation.annotation,
+ });
+ } else {
+ finalResults.annotationResultsRect.push({
+ coordinates: {
+ x0: timestamp,
+ x1: endTimestamp,
+ },
+ details: annotation.annotation,
+ });
+ }
+ });
+
+ const modelSnapshots = modelSnapshotsResp?.model_snapshots ?? [];
+ modelSnapshots.forEach((modelSnapshot) => {
+ const timestamp = Number(modelSnapshot?.timestamp);
+
+ finalResults.modelSnapshotResultsLine.push({
+ dataValue: timestamp,
+ details: modelSnapshot.description,
+ });
+ });
+
return finalResults;
}
From e580d5a1e2936bb9357d8ec5f12f7c50653937d8 Mon Sep 17 00:00:00 2001
From: Constance
Date: Tue, 22 Jun 2021 14:43:54 -0700
Subject: [PATCH 10/78] [App Search] Convert Result Settings & Relevance Tuning
pages to new page template (#102845)
* Convert Result Settings page to new page template
+ remove wrapper around empty state (auto handled by new page template)
+ update tests w/ new test helpers
* Convert Relevance Tuning page to new page template
- Remove old relevance_tuning_layout (which handled breadcrumbs, page header, flash messages, and callouts) in favor of simply using the new templtate + callouts (yay DRYing)
- Remove panel wrapper around empty state (handled by new page template)
* Update router
* [Polish] Spacing & icon polish from Davey
see https://github.com/elastic/kibana/pull/101958/files
---
.../components/engine/engine_router.tsx | 20 ++--
.../components/empty_state.tsx | 62 ++++++-----
.../relevance_tuning.test.tsx | 57 +++++-----
.../relevance_tuning/relevance_tuning.tsx | 74 +++++++++----
.../relevance_tuning_form.tsx | 2 +-
.../relevance_tuning_layout.test.tsx | 64 -----------
.../relevance_tuning_layout.tsx | 73 -------------
.../relevance_tuning_preview.tsx | 1 +
.../components/empty_state.tsx | 62 ++++++-----
.../result_settings/result_settings.test.tsx | 56 ++++------
.../result_settings/result_settings.tsx | 101 +++++++++---------
11 files changed, 224 insertions(+), 348 deletions(-)
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
index 91a21847107a9..04e252e44270b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
@@ -114,6 +114,16 @@ export const EngineRouter: React.FC = () => {
)}
+ {canManageEngineRelevanceTuning && (
+
+
+
+ )}
+ {canManageEngineResultSettings && (
+
+
+
+ )}
{canManageEngineSearchUi && (
@@ -131,21 +141,11 @@ export const EngineRouter: React.FC = () => {
)}
- {canManageEngineRelevanceTuning && (
-
-
-
- )}
{canManageEngineSynonyms && (
)}
- {canManageEngineResultSettings && (
-
-
-
- )}
{canViewMetaEngineSourceEngines && (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx
index e6a14d7b5cd72..df29010bd682f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx
@@ -7,42 +7,40 @@
import React from 'react';
-import { EuiButton, EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DOCS_PREFIX } from '../../../routes';
export const EmptyState: React.FC = () => (
-
-
- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title', {
- defaultMessage: 'Add documents to tune relevance',
- })}
-
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title', {
+ defaultMessage: 'Add documents to tune relevance',
+ })}
+
+ }
+ body={i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description',
+ {
+ defaultMessage:
+ 'A schema will be automatically created for you after you index some documents.',
}
- body={i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description',
- {
- defaultMessage:
- 'A schema will be automatically created for you after you index some documents.',
- }
- )}
- actions={
-
- {i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel',
- { defaultMessage: 'Read the relevance tuning guide' }
- )}
-
- }
- />
-
+ )}
+ actions={
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel',
+ { defaultMessage: 'Read the relevance tuning guide' }
+ )}
+
+ }
+ />
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx
index 092740ac5d3cc..48b536a954ed5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx
@@ -13,14 +13,14 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiEmptyPrompt } from '@elastic/eui';
-
-import { Loading } from '../../../shared/loading';
import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt';
+import { getPageHeaderActions } from '../../../test_helpers';
-import { EmptyState } from './components';
import { RelevanceTuning } from './relevance_tuning';
+
+import { RelevanceTuningCallouts } from './relevance_tuning_callouts';
import { RelevanceTuningForm } from './relevance_tuning_form';
+import { RelevanceTuningPreview } from './relevance_tuning_preview';
describe('RelevanceTuning', () => {
const values = {
@@ -50,9 +50,9 @@ describe('RelevanceTuning', () => {
it('renders', () => {
const wrapper = subject();
+ expect(wrapper.find(RelevanceTuningCallouts).exists()).toBe(true);
expect(wrapper.find(RelevanceTuningForm).exists()).toBe(true);
- expect(wrapper.find(Loading).exists()).toBe(false);
- expect(wrapper.find(EmptyState).exists()).toBe(false);
+ expect(wrapper.find(RelevanceTuningPreview).exists()).toBe(true);
});
it('initializes relevance tuning data', () => {
@@ -60,33 +60,38 @@ describe('RelevanceTuning', () => {
expect(actions.initializeRelevanceTuning).toHaveBeenCalled();
});
- it('will render an empty message when the engine has no schema', () => {
+ it('will prevent user from leaving the page if there are unsaved changes', () => {
setMockValues({
...values,
- engineHasSchemaFields: false,
+ unsavedChanges: true,
});
- const wrapper = subject();
- expect(wrapper.find(EmptyState).dive().find(EuiEmptyPrompt).exists()).toBe(true);
- expect(wrapper.find(Loading).exists()).toBe(false);
- expect(wrapper.find(RelevanceTuningForm).exists()).toBe(false);
+ expect(subject().find(UnsavedChangesPrompt).prop('hasUnsavedChanges')).toBe(true);
});
- it('will show a loading message if data is loading', () => {
- setMockValues({
- ...values,
- dataLoading: true,
+ describe('header actions', () => {
+ it('renders a Save button that will save the current changes', () => {
+ const buttons = getPageHeaderActions(subject());
+ expect(buttons.children().length).toBe(2);
+ const saveButton = buttons.find('[data-test-subj="SaveRelevanceTuning"]');
+ saveButton.simulate('click');
+ expect(actions.updateSearchSettings).toHaveBeenCalled();
});
- const wrapper = subject();
- expect(wrapper.find(Loading).exists()).toBe(true);
- expect(wrapper.find(EmptyState).exists()).toBe(false);
- expect(wrapper.find(RelevanceTuningForm).exists()).toBe(false);
- });
- it('will prevent user from leaving the page if there are unsaved changes', () => {
- setMockValues({
- ...values,
- unsavedChanges: true,
+ it('renders a Reset button that will remove all weights and boosts', () => {
+ const buttons = getPageHeaderActions(subject());
+ expect(buttons.children().length).toBe(2);
+ const resetButton = buttons.find('[data-test-subj="ResetRelevanceTuning"]');
+ resetButton.simulate('click');
+ expect(actions.resetSearchSettings).toHaveBeenCalled();
+ });
+
+ it('will not render buttons if the engine has no schema', () => {
+ setMockValues({
+ ...values,
+ engineHasSchemaFields: false,
+ });
+ const buttons = getPageHeaderActions(subject());
+ expect(buttons.children().length).toBe(0);
});
- expect(subject().find(UnsavedChangesPrompt).prop('hasUnsavedChanges')).toBe(true);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx
index b98541a963890..2e87d6836199b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx
@@ -9,43 +9,77 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { Loading } from '../../../shared/loading';
+import { SAVE_BUTTON_LABEL } from '../../../shared/constants';
import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt';
+import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants';
+import { getEngineBreadcrumbs } from '../engine';
+import { AppSearchPageTemplate } from '../layout';
import { EmptyState } from './components';
+import { RELEVANCE_TUNING_TITLE } from './constants';
+import { RelevanceTuningCallouts } from './relevance_tuning_callouts';
import { RelevanceTuningForm } from './relevance_tuning_form';
-import { RelevanceTuningLayout } from './relevance_tuning_layout';
import { RelevanceTuningPreview } from './relevance_tuning_preview';
import { RelevanceTuningLogic } from '.';
export const RelevanceTuning: React.FC = () => {
const { dataLoading, engineHasSchemaFields, unsavedChanges } = useValues(RelevanceTuningLogic);
- const { initializeRelevanceTuning } = useActions(RelevanceTuningLogic);
+ const { initializeRelevanceTuning, resetSearchSettings, updateSearchSettings } = useActions(
+ RelevanceTuningLogic
+ );
useEffect(() => {
initializeRelevanceTuning();
}, []);
- if (dataLoading) return ;
-
return (
-
+
+ {SAVE_BUTTON_LABEL}
+ ,
+
+ {RESTORE_DEFAULTS_BUTTON_LABEL}
+ ,
+ ]
+ : [],
+ }}
+ isLoading={dataLoading}
+ isEmptyState={!engineHasSchemaFields}
+ emptyState={ }
+ >
- {engineHasSchemaFields ? (
-
-
-
-
-
-
-
-
- ) : (
-
- )}
-
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx
index 5cbd291f85deb..c35cd280c7a05 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx
@@ -42,7 +42,7 @@ export const RelevanceTuningForm: React.FC = () => {
return (
+ >
);
}
From 4fa3dc46cb14f041d21b1b6a06961eb490602701 Mon Sep 17 00:00:00 2001
From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
Date: Tue, 22 Jun 2021 18:56:33 -0400
Subject: [PATCH 13/78] [RAC] T-Grid is moving to a new home (#100265)
* wip
* First pass at standalone and embedded redux stores and usage
* wip
* First pass at standalone and embedded redux stores and usage
* wip
* clean up
* wip
* refact(NA): remove extra pkg_npm target and add specific target folders on @kbn/i18n
* cleanup
* - fixes type errors in tests
* WIP remove use_manage_timeline
* wip add query + selector
* finishing integrating timeline manage context from redux
* integrating t-grid in security solution
* fix RowRender type
* WIP begin to move components from package to plugin
* integration of t-grid inside of security solution
* wip to make redux work
* little trick to make it render
* - fixes a few type errors
* better integration betwen tgrid and security solutions
* bringing back tsconfig on timeline
* wip integration t-grid in observability
* fix types
* fix type in security solutions
* add type to import + trie dto get the bundle size as small as possible
* fix type in integration test
* fix type in integration test
* - fix tests
* clean up to use technical fields
* - fixes unit tests
* - mocks the `useDateFormat` function of the `useKibana` service to fix unit tests
* fix t-grid settings vs create timeline + fix inspect button
* fix last suites test
* Update unit tests, snapshots and lint
* Fix bad merge
* fix plugin export
* Fix some failing tests
* fix unit tets in timelines plugins
* fix latest test
* fix i18n
* free obs from t-grid
* Fix timeline functional plugin types
* fix store provider
* Update failing defaultHeader test
* Fix i18n usage in security solution
* Fix remaining i18n errors in timelines plugin
* Dedupe common shared types
* move drag and drop utils in package to avoid duplication
* More shared type cleanup
* add feature flag
* review I
* fix merge with master
* fix i18n translation
* More type deduping
* Use @kbn/common-utils, fix remaining types
* fix types
* fix tests
* missing type
* fix cypress tests
Co-authored-by: Kevin Qualters
Co-authored-by: Tiago Costa
Co-authored-by: Andrew Goldstein
---
.eslintrc.js | 12 +-
package.json | 4 +
packages/BUILD.bazel | 3 +-
packages/kbn-optimizer/limits.yml | 4 +-
.../kbn-securitysolution-t-grid/BUILD.bazel | 125 +
.../kbn-securitysolution-t-grid/README.md | 3 +
.../babel.config.js | 19 +
.../jest.config.js | 13 +
.../kbn-securitysolution-t-grid/package.json | 10 +
.../react/package.json | 5 +
.../src/constants/index.ts | 26 +
.../kbn-securitysolution-t-grid/src/index.ts | 11 +
.../src/mock/index.ts | 9 +
.../src/mock}/mock_event_details.ts | 5 +-
.../src/utils/api/index.ts | 42 +
.../src/utils/drag_and_drop/index.ts | 133 +
.../src/utils/index.ts | 10 +
.../tsconfig.browser.json | 23 +
.../kbn-securitysolution-t-grid/tsconfig.json | 19 +
packages/kbn-test/jest-preset.js | 2 +-
tsconfig.json | 1 -
tsconfig.refs.json | 1 +
.../common/experimental_features.ts | 1 +
.../plugins/security_solution/common/index.ts | 4 +
.../common/search_strategy/common/index.ts | 68 +-
.../common/search_strategy/index.ts | 1 +
.../timeline/events/all/index.ts | 41 +-
.../timeline/events/common/index.ts | 24 +-
.../timeline/events/details/index.ts | 29 +-
.../timeline/events/eql/index.ts | 47 +-
.../timeline/events/last_event_time/index.ts | 43 +-
.../common/search_strategy/timeline/index.ts | 26 +-
.../security_solution/common/types/index.ts | 8 +
.../common/types/timeline/actions/index.ts | 14 +
.../common/types/timeline/cells/index.ts | 8 +
.../common/types/timeline/columns/index.ts | 13 +
.../types/timeline/data_provider/index.ts | 15 +
.../common/types/timeline/index.ts | 12 +
.../common/types/timeline/rows/index.ts | 7 +
.../common/types/timeline/store.ts | 97 +
.../common/utils/field_formatters.test.ts | 2 +-
.../integration/overview/overview.spec.ts | 2 +-
.../cypress/support/commands.js | 2 +-
x-pack/plugins/security_solution/kibana.json | 1 +
.../security_solution/public/app/app.tsx | 33 +-
.../common/components/accessibility/index.ts | 8 +
.../tooltip_with_keyboard_shortcut/index.tsx | 4 +-
.../components/alerts_viewer/alerts_table.tsx | 30 +-
.../alerts_viewer/default_headers.ts | 4 +-
.../charts/draggable_legend.test.tsx | 2 +
.../charts/draggable_legend_item.test.tsx | 2 +
.../drag_drop_context_wrapper.test.tsx | 2 +
.../drag_drop_context_wrapper.tsx | 17 +-
.../drag_and_drop/draggable_wrapper.test.tsx | 2 +
.../drag_and_drop/draggable_wrapper.tsx | 8 +-
.../draggable_wrapper_hover_content.test.tsx | 85 +-
.../draggable_wrapper_hover_content.tsx | 27 +-
.../drag_and_drop/droppable_wrapper.test.tsx | 2 +
.../components/drag_and_drop/helpers.test.ts | 2 +-
.../components/drag_and_drop/helpers.ts | 364 +-
.../components/draggables/index.test.tsx | 2 +
.../event_details/alert_summary_view.test.tsx | 2 +
.../components/event_details/columns.tsx | 4 +-
.../event_details/event_details.test.tsx | 2 +
.../event_fields_browser.test.tsx | 2 +
.../event_details/event_fields_browser.tsx | 8 +-
.../components/event_details/helpers.tsx | 4 +-
.../events_viewer/default_headers.tsx | 2 +-
.../events_viewer/events_viewer.test.tsx | 13 +-
.../events_viewer/events_viewer.tsx | 34 +-
.../components/events_viewer/index.test.tsx | 6 +-
.../common/components/events_viewer/index.tsx | 102 +-
.../components/events_viewer/translations.ts | 7 -
.../components/header_page/title.test.tsx | 2 +
.../common/components/inspect/index.tsx | 4 +-
.../components/ml/entity_draggable.test.tsx | 2 +
.../ml/score/anomaly_score.test.tsx | 2 +
.../ml/score/anomaly_scores.test.tsx | 2 +
.../get_anomalies_host_table_columns.test.tsx | 2 +
...t_anomalies_network_table_columns.test.tsx | 2 +
.../common/components/tables/helpers.test.tsx | 3 +
.../common/components/toasters/utils.ts | 2 +-
.../common/components/top_n/index.test.tsx | 38 +-
.../components/with_hover_actions/index.tsx | 8 +-
.../events/last_event_time/index.ts | 2 +-
.../public/common/containers/source/index.tsx | 4 +-
.../common/hooks/use_app_toasts.test.ts | 3 +-
.../public/common/hooks/use_app_toasts.ts | 8 +-
.../lib/clipboard/with_copy_to_clipboard.tsx | 3 +-
.../common/lib/kibana/__mocks__/index.ts | 16 +-
.../public/common/mock/global_state.ts | 1 +
.../public/common/mock/header.ts | 2 +-
.../mock/mock_timeline_control_columns.tsx | 2 +-
.../public/common/mock/utils.ts | 32 +-
.../public/common/store/inputs/model.ts | 1 +
.../public/common/store/types.ts | 12 -
.../components/alerts_table/actions.tsx | 4 +-
.../alerts_utility_bar/index.test.tsx | 1 +
.../alerts_table/default_config.tsx | 4 +-
.../components/alerts_table/index.tsx | 76 +-
.../examples/observablity_alerts/columns.ts | 3 +-
.../render_cell_value.test.tsx | 4 +-
.../examples/security_solution_rac/columns.ts | 3 +-
.../render_cell_value.test.tsx | 4 +-
.../security_solution_detections/columns.ts | 2 +-
.../render_cell_value.test.tsx | 4 +-
.../alerts/use_signal_index.tsx | 2 +-
.../lists/use_lists_index.tsx | 3 +-
.../rules/use_rule_status.tsx | 2 +-
.../rules/use_rule_with_fallback.test.tsx | 2 +-
.../rules/use_rule_with_fallback.tsx | 2 +-
.../detection_engine/detection_engine.tsx | 2 +-
.../all/exceptions/exceptions_table.test.tsx | 3 +
.../rules/all/exceptions/exceptions_table.tsx | 5 +-
.../rules/all/rules_tables.tsx | 12 +-
.../detection_engine/rules/details/index.tsx | 4 +-
.../components/hosts_table/index.test.tsx | 2 +
.../uncommon_process_table/index.test.tsx | 2 +
.../hosts/pages/details/details_tabs.test.tsx | 2 +
.../public/hosts/pages/hosts.tsx | 2 +-
.../navigation/events_query_tab_body.tsx | 15 +-
.../plugins/security_solution/public/index.ts | 1 +
.../network/components/ip/index.test.tsx | 2 +
.../network_dns_table/index.test.tsx | 2 +
.../network_http_table/index.test.tsx | 1 +
.../index.test.tsx | 2 +
.../network_top_n_flow_table/index.test.tsx | 1 +
.../network/components/port/index.test.tsx | 2 +
.../source_destination/index.test.tsx | 2 +
.../source_destination_ip.test.tsx | 2 +
.../components/tls_table/index.test.tsx | 2 +
.../components/users_table/index.test.tsx | 2 +
.../public/network/pages/network.tsx | 2 +-
.../endpoint_overview/index.test.tsx | 2 +
.../security_solution/public/plugin.tsx | 19 +-
.../certificate_fingerprint/index.test.tsx | 2 +
.../components/duration/index.test.tsx | 2 +
.../field_renderers/field_renderers.test.tsx | 2 +
.../fields_browser/categories_pane.tsx | 2 +-
.../fields_browser/category.test.tsx | 3 +
.../components/fields_browser/category.tsx | 4 +-
.../fields_browser/category_columns.tsx | 12 +-
.../fields_browser/category_title.test.tsx | 43 +-
.../fields_browser/field_browser.tsx | 8 +-
.../fields_browser/field_items.test.tsx | 4 +-
.../components/fields_browser/field_items.tsx | 9 +-
.../fields_browser/field_name.test.tsx | 2 +
.../components/fields_browser/field_name.tsx | 2 +-
.../fields_browser/fields_pane.test.tsx | 2 +
.../components/fields_browser/fields_pane.tsx | 2 +-
.../components/fields_browser/header.test.tsx | 3 +-
.../components/fields_browser/header.tsx | 13 +-
.../components/fields_browser/helpers.tsx | 2 +-
.../components/fields_browser/index.test.tsx | 2 +
.../components/fields_browser/types.ts | 2 +-
.../components/flyout/bottom_bar/index.tsx | 2 +-
.../components/ja3_fingerprint/index.test.tsx | 2 +
.../components/manage_timeline/index.test.tsx | 125 -
.../components/manage_timeline/index.tsx | 212 -
.../components/netflow/index.test.tsx | 2 +
.../components/notes/note_cards/index.tsx | 2 +-
.../components/open_timeline/helpers.test.ts | 2 +-
.../components/open_timeline/helpers.ts | 3 +-
.../timeline/body/actions/header_actions.tsx | 25 +-
.../timeline/body/actions/index.test.tsx | 4 -
.../timeline/body/actions/index.tsx | 65 +-
.../body/column_headers/actions/index.tsx | 2 +-
.../body/column_headers/column_header.tsx | 14 +-
.../body/column_headers/default_headers.ts | 3 +-
.../body/column_headers/filter/index.tsx | 2 +-
.../column_headers/header/header_content.tsx | 2 +-
.../body/column_headers/header/helpers.ts | 7 +-
.../body/column_headers/header/index.test.tsx | 12 +-
.../body/column_headers/header/index.tsx | 20 +-
.../header_tooltip_content/index.test.tsx | 3 +-
.../header_tooltip_content/index.tsx | 2 +-
.../timeline/body/column_headers/helpers.ts | 2 +-
.../body/column_headers/index.test.tsx | 2 +
.../timeline/body/column_headers/index.tsx | 11 +-
.../timeline/body/control_columns/index.tsx | 45 +-
.../body/data_driven_columns/index.test.tsx | 2 -
.../body/data_driven_columns/index.tsx | 29 +-
.../stateful_cell.test.tsx | 8 +-
.../data_driven_columns/stateful_cell.tsx | 8 +-
.../body/events/event_column_view.test.tsx | 12 -
.../body/events/event_column_view.tsx | 25 +-
.../components/timeline/body/events/index.tsx | 12 +-
.../timeline/body/events/stateful_event.tsx | 26 +-
.../events/stateful_row_renderer/index.tsx | 6 +-
.../events/use_stateful_event_focus/index.tsx | 2 +-
.../components/timeline/body/index.test.tsx | 8 +-
.../components/timeline/body/index.tsx | 30 +-
.../timeline/body/renderers/args.test.tsx | 2 +
.../renderers/auditd/generic_details.test.tsx | 2 +
.../auditd/generic_file_details.test.tsx | 2 +
.../auditd/generic_row_renderer.test.tsx | 4 +-
.../renderers/auditd/generic_row_renderer.tsx | 4 +-
.../primary_secondary_user_info.test.tsx | 2 +
.../session_user_host_working_dir.test.tsx | 2 +
.../body/renderers/bytes/index.test.tsx | 2 +
.../body/renderers/column_renderer.ts | 2 +-
.../renderers/cti/threat_match_row.test.tsx | 2 +
.../cti/threat_match_row_renderer.tsx | 3 +-
.../body/renderers/cti/threat_match_rows.tsx | 3 +-
.../dns/dns_request_event_details.test.tsx | 2 +
.../dns_request_event_details_line.test.tsx | 2 +
.../renderers/empty_column_renderer.test.tsx | 2 +
.../body/renderers/empty_column_renderer.tsx | 3 +-
.../endgame_security_event_details.test.tsx | 2 +
...dgame_security_event_details_line.test.tsx | 2 +
.../renderers/exit_code_draggable.test.tsx | 2 +
.../body/renderers/file_draggable.test.tsx | 2 +
.../body/renderers/file_hash.test.tsx | 2 +
.../renderers/get_column_renderer.test.tsx | 2 +
.../body/renderers/get_row_renderer.test.tsx | 2 +
.../body/renderers/get_row_renderer.ts | 2 +-
.../body/renderers/host_working_dir.test.tsx | 2 +
.../timeline/body/renderers/index.ts | 2 +-
.../netflow/netflow_row_renderer.test.tsx | 2 +
.../netflow/netflow_row_renderer.tsx | 4 +-
.../parent_process_draggable.test.tsx | 2 +
.../renderers/plain_column_renderer.test.tsx | 2 +
.../body/renderers/plain_column_renderer.tsx | 2 +-
.../body/renderers/plain_row_renderer.tsx | 4 +-
.../body/renderers/process_draggable.test.tsx | 2 +
.../body/renderers/process_hash.test.tsx | 2 +
.../registry/registry_event_details.test.tsx | 2 +
.../registry_event_details_line.test.tsx | 2 +
.../timeline/body/renderers/row_renderer.tsx | 18 -
.../suricata/suricata_details.test.tsx | 2 +
.../suricata/suricata_row_renderer.test.tsx | 2 +
.../suricata/suricata_row_renderer.tsx | 4 +-
.../suricata/suricata_signature.test.tsx | 2 +
.../renderers/system/generic_details.test.tsx | 2 +
.../system/generic_file_details.test.tsx | 2 +
.../system/generic_row_renderer.test.tsx | 4 +-
.../renderers/system/generic_row_renderer.tsx | 4 +-
.../body/renderers/system/package.test.tsx | 2 +
.../renderers/user_host_working_dir.test.tsx | 2 +
.../body/renderers/zeek/zeek_details.test.tsx | 2 +
.../renderers/zeek/zeek_row_renderer.test.tsx | 2 +
.../body/renderers/zeek/zeek_row_renderer.tsx | 4 +-
.../renderers/zeek/zeek_signature.test.tsx | 2 +
.../components/timeline/body/sort/index.ts | 12 +-
.../timeline/body/sort/sort_indicator.tsx | 2 +-
.../default_cell_renderer.test.tsx | 2 +
.../timeline/cell_rendering/index.tsx | 14 +-
.../timeline/data_providers/index.test.tsx | 35 +-
.../timeline/data_providers/index.tsx | 14 +-
.../data_providers/provider_item_badge.tsx | 15 +-
.../data_providers/providers.test.tsx | 85 +-
.../timeline/data_providers/providers.tsx | 11 +-
.../timeline/eql_tab_content/index.test.tsx | 5 +
.../timeline/eql_tab_content/index.tsx | 38 +-
.../timelines/components/timeline/events.ts | 49 +-
.../components/timeline/footer/index.test.tsx | 2 +
.../components/timeline/footer/index.tsx | 36 +-
.../timeline/footer/translations.ts | 7 +
.../timelines/components/timeline/helpers.tsx | 2 +-
.../timelines/components/timeline/index.tsx | 5 +-
.../pinned_tab_content/index.test.tsx | 5 +
.../timeline/pinned_tab_content/index.tsx | 11 +-
.../components/timeline/query_bar/index.tsx | 2 +-
.../timeline/query_tab_content/index.test.tsx | 9 +
.../timeline/query_tab_content/index.tsx | 47 +-
.../timeline/search_or_filter/index.tsx | 8 +-
.../search_or_filter/search_or_filter.tsx | 2 +-
.../timeline/tabs_content/index.tsx | 8 +-
.../timelines/containers/details/index.tsx | 2 +-
.../public/timelines/containers/index.tsx | 8 +-
.../timelines/containers/kpis/index.tsx | 2 +-
.../containers/local_storage/index.tsx | 4 +-
.../timelines/store/timeline/actions.ts | 135 +-
.../timelines/store/timeline/defaults.ts | 3 +-
.../public/timelines/store/timeline/epic.ts | 12 +-
.../timelines/store/timeline/helpers.ts | 57 +-
.../public/timelines/store/timeline/model.ts | 78 +-
.../timelines/store/timeline/reducer.test.ts | 3 +-
.../timelines/store/timeline/reducer.ts | 209 +-
.../timelines/store/timeline/selectors.ts | 3 +
.../plugins/security_solution/public/types.ts | 2 +
.../reference_rules/__mocks__/eql.ts | 791 +
.../reference_rules/eql.test.ts | 4 +-
.../timelines/pick_saved_timeline.test.ts | 27 +-
.../timelines/pick_saved_timeline.ts | 5 +-
.../security_solution/server/plugin.ts | 23 -
.../factory/hosts/details/index.test.tsx | 1 +
.../factory/events/all/helpers.test.ts | 1515 -
.../plugins/security_solution/tsconfig.json | 1 +
x-pack/plugins/timelines/README.md | 4 +-
x-pack/plugins/timelines/common/constants.ts | 8 +
.../timelines/common/ecs/agent/index.ts | 10 +
.../timelines/common/ecs/auditd/index.ts | 46 +
.../timelines/common/ecs/cloud/index.ts | 21 +
.../timelines/common/ecs/destination/index.ts | 22 +
.../plugins/timelines/common/ecs/dns/index.ts | 20 +
.../common/ecs/ecs_fields/extend_map.test.ts | 57 +
.../common/ecs/ecs_fields/extend_map.ts | 15 +
.../timelines/common/ecs/ecs_fields/index.ts | 359 +
.../timelines/common/ecs/endgame/index.ts | 22 +
.../timelines/common/ecs/event/index.ts | 46 +
.../timelines/common/ecs/file/index.ts | 61 +
.../plugins/timelines/common/ecs/geo/index.ts | 21 +
.../timelines/common/ecs/host/index.ts | 36 +
.../timelines/common/ecs/http/index.ts | 38 +
x-pack/plugins/timelines/common/ecs/index.ts | 66 +
.../timelines/common/ecs/network/index.ts | 15 +
.../timelines/common/ecs/process/index.ts | 40 +
.../timelines/common/ecs/ransomware/index.ts | 30 +
.../timelines/common/ecs/registry/index.ts | 13 +
.../timelines/common/ecs/rule/index.ts | 43 +
.../timelines/common/ecs/signal/index.ts | 18 +
.../timelines/common/ecs/source/index.ts | 17 +
.../timelines/common/ecs/suricata/index.ts | 24 +
.../timelines/common/ecs/system/index.ts | 40 +
.../timelines/common/ecs/threat/index.ts | 25 +
.../plugins/timelines/common/ecs/tls/index.ts | 34 +
.../plugins/timelines/common/ecs/url/index.ts | 16 +
.../timelines/common/ecs/user/index.ts | 22 +
.../timelines/common/ecs/winlog/index.ts | 10 +
.../timelines/common/ecs/zeek/index.ts | 134 +
x-pack/plugins/timelines/common/index.ts | 4 +
.../common/search_strategy/common/index.ts | 80 +
.../common/search_strategy/eql/index.ts | 45 +
.../eql/validation/helpers.mock.ts | 70 +
.../eql/validation/helpers.test.ts | 59 +
.../search_strategy/eql/validation/helpers.ts | 35 +
.../search_strategy/eql/validation/index.ts | 8 +
.../timelines/common/search_strategy/index.ts | 11 +
.../search_strategy/index_fields/index.ts | 89 +
.../timeline/events/all/index.ts | 42 +
.../timeline/events/common/index.ts | 26 +
.../timeline/events/details/index.ts | 31 +
.../timeline/events/eql/index.ts | 46 +
.../search_strategy/timeline/events/index.ts | 18 +
.../timeline/events/last_event_time/index.ts | 42 +
.../common/search_strategy/timeline/index.ts | 197 +
x-pack/plugins/timelines/common/typed_json.ts | 57 +
.../plugins/timelines/common/types/index.ts | 8 +
.../common/types/timeline/actions/index.ts | 92 +
.../common/types/timeline/cells/index.ts | 21 +
.../common/types/timeline/columns/index.ts | 54 +
.../types/timeline/data_provider/index.ts | 65 +
.../timelines/common/types/timeline/index.ts | 744 +
.../common/types/timeline/note/index.ts | 127 +
.../types/timeline/pinned_event/index.ts | 85 +
.../common/types/timeline/rows/index.ts | 24 +
.../timelines/common/types/timeline/store.ts | 98 +
.../plugins/timelines/common/utility_types.ts | 53 +
.../utils}/accessibility/helpers.test.tsx | 0
.../common/utils}/accessibility/helpers.ts | 8 +-
.../common/utils/accessibility/index.ts | 8 +
.../common/utils/api.ts} | 0
.../common/utils/field_formatters.test.ts | 196 +
.../common/utils/field_formatters.ts | 153 +
.../timelines/common/utils/to_array.ts | 87 +
x-pack/plugins/timelines/jest.config.js | 12 +
x-pack/plugins/timelines/kibana.json | 3 +-
.../draggable_keyboard_wrapper_hook/index.tsx | 8 +-
.../components/drag_and_drop/helpers.ts | 211 +
.../public/components/drag_and_drop/index.tsx | 93 +
.../draggables/field_badge/index.tsx | 48 +
.../draggables/field_badge/translations.ts | 34 +
.../public/components/draggables/index.tsx | 8 +
.../exit_full_screen/index.test.tsx | 60 +
.../components/exit_full_screen/index.tsx | 64 +
.../exit_full_screen/translations.ts | 12 +
.../timelines/public/components/index.tsx | 57 +-
.../public/components/inspect/index.test.tsx | 105 +
.../public/components/inspect/index.tsx | 114 +
.../public/components/inspect/modal.test.tsx | 282 +
.../public/components/inspect/modal.tsx | 253 +
.../public/components/inspect/translations.ts | 64 +
.../components/last_updated/index.test.tsx | 2 +-
.../public}/components/last_updated/index.tsx | 5 +-
.../components/last_updated/translations.ts | 4 +-
.../public/components/loading/index.tsx | 98 +
.../__snapshots__/index.test.tsx.snap | 526 +
.../body/column_headers/actions/index.tsx | 69 +
.../body/column_headers/column_header.tsx | 310 +
.../common/dragging_container.tsx | 25 +
.../body/column_headers/common/styles.tsx | 19 +
.../body/column_headers/default_headers.ts | 58 +
.../header/__snapshots__/index.test.tsx.snap | 51 +
.../column_headers/header/header_content.tsx | 85 +
.../body/column_headers/header/helpers.ts | 55 +
.../body/column_headers/header/index.test.tsx | 331 +
.../body/column_headers/header/index.tsx | 94 +
.../__snapshots__/index.test.tsx.snap | 66 +
.../header_tooltip_content/index.test.tsx | 72 +
.../header_tooltip_content/index.tsx | 81 +
.../body/column_headers/helpers.test.ts | 116 +
.../t_grid/body/column_headers/helpers.ts | 57 +
.../t_grid/body/column_headers/index.test.tsx | 316 +
.../t_grid/body/column_headers/index.tsx | 295 +
.../body/column_headers/translations.ts | 51 +
.../components/t_grid/body/constants.ts | 29 +
.../__snapshots__/index.test.tsx.snap | 967 +
.../body/data_driven_columns/index.test.tsx | 57 +
.../t_grid/body/data_driven_columns/index.tsx | 394 +
.../stateful_cell.test.tsx | 173 +
.../data_driven_columns/stateful_cell.tsx | 65 +
.../body/data_driven_columns/translations.ts | 28 +
.../body/events/event_column_view.test.tsx | 115 +
.../t_grid/body/events/event_column_view.tsx | 182 +
.../components/t_grid/body/events/index.tsx | 100 +
.../t_grid/body/events/stateful_event.tsx | 207 +
.../body/events/stateful_event_context.tsx | 17 +
.../events/stateful_row_renderer/index.tsx | 104 +
.../t_grid/body/events/translations.ts | 15 +
.../events/use_stateful_event_focus/index.tsx | 96 +
.../components/t_grid/body/helpers.test.ts | 178 +
.../public/components/t_grid/body/helpers.tsx | 64 +
.../components/t_grid/body/index.test.tsx | 132 +
.../public/components/t_grid/body/index.tsx | 334 +
.../plain_row_renderer.test.tsx.snap | 3 +
.../body/renderers/get_column_renderer.ts | 24 +
.../t_grid/body/renderers/get_row_renderer.ts | 12 +
.../renderers/plain_row_renderer.test.tsx | 45 +
.../body/renderers/plain_row_renderer.tsx | 22 +
.../t_grid/body/renderers/row_renderer.tsx | 21 +
.../sort_indicator.test.tsx.snap | 18 +
.../components/t_grid/body/sort/index.ts | 16 +
.../t_grid/body/sort/sort_indicator.test.tsx | 85 +
.../t_grid/body/sort/sort_indicator.tsx | 68 +
.../t_grid/body/sort/sort_number.tsx | 27 +
.../components/t_grid/body/translations.ts | 229 +
.../components/t_grid/footer/index.test.tsx | 259 +
.../public/components/t_grid/footer/index.tsx | 394 +
.../components/t_grid/footer/translations.ts | 39 +
.../__snapshots__/index.test.tsx.snap | 35 +
.../t_grid/header_section/index.test.tsx | 159 +
.../t_grid/header_section/index.tsx | 106 +
.../public/components/t_grid/helpers.test.tsx | 578 +
.../public/components/t_grid/helpers.tsx | 314 +
.../components/t_grid/integrated/index.tsx | 355 +
.../t_grid/integrated/translations.ts | 36 +
.../components/t_grid/standalone/index.tsx | 339 +
.../t_grid/standalone/translations.ts | 36 +
.../public/components/t_grid/styles.tsx | 460 +
.../__snapshots__/index.test.tsx.snap | 11 +
.../components/t_grid/subtitle/index.test.tsx | 71 +
.../components/t_grid/subtitle/index.tsx | 72 +
.../public/components/t_grid/translations.ts | 20 +
.../public/components/t_grid/types.ts | 17 +
.../timelines/public/components/tgrid.tsx | 25 +
.../__snapshots__/index.test.tsx.snap | 19 +
.../truncatable_text/index.test.tsx | 36 +
.../components/truncatable_text/index.tsx | 28 +
.../public/components/utils/helpers.ts | 28 +
.../components/utils/keury/index.test.ts | 65 +
.../public/components/utils/keury/index.ts | 99 +
.../components/utils/use_mount_appended.ts | 31 +
.../timelines/public/container/index.tsx | 346 +
.../public/container/translations.ts | 22 +
.../public/hooks/use_add_to_timeline.ts} | 47 +-
.../timelines/public/hooks/use_app_toasts.ts | 241 +
.../timelines/public/hooks/use_selector.tsx | 20 +
x-pack/plugins/timelines/public/index.scss | 0
x-pack/plugins/timelines/public/index.ts | 47 +-
.../timelines/public/methods/index.tsx | 33 +-
.../timelines/public/mock/browser_fields.ts | 737 +
.../timelines/public/mock/cell_renderer.tsx | 20 +
.../timelines/public/mock/global_state.ts | 53 +
.../plugins/timelines/public/mock/header.ts | 133 +
x-pack/plugins/timelines/public/mock/index.ts | 16 +
.../timelines/public/mock/index_pattern.ts | 112 +
.../public/mock/kibana_react.mock.ts | 36 +
.../public/mock/mock_and_providers.tsx | 93 +
.../public/mock/mock_data_providers.tsx | 59 +
.../public/mock/mock_local_storage.ts | 35 +
.../mock/mock_timeline_control_columns.tsx | 117 +
.../public/mock/mock_timeline_data.ts | 1511 +
.../timelines/public/mock/plugin_mock.tsx | 27 +
.../timelines/public/mock/test_providers.tsx | 57 +
x-pack/plugins/timelines/public/plugin.ts | 62 +-
.../timelines/public/store/t_grid/actions.ts | 103 +
.../timelines/public/store/t_grid/defaults.ts | 103 +
.../timelines/public/store/t_grid/helpers.ts | 424 +
.../timelines/public/store/t_grid/index.ts | 65 +
.../timelines/public/store/t_grid/inputs.ts | 13 +
.../timelines/public/store/t_grid/model.ts | 128 +
.../timelines/public/store/t_grid/reducer.ts | 212 +
.../public/store/t_grid/selectors.ts | 48 +
.../public/store/t_grid/translations.ts | 32 +
.../timelines/public/store/t_grid/types.ts | 67 +
x-pack/plugins/timelines/public/types.ts | 45 +-
x-pack/plugins/timelines/server/config.ts | 2 +-
x-pack/plugins/timelines/server/index.ts | 2 +-
x-pack/plugins/timelines/server/plugin.ts | 24 +-
.../index_fields/index.test.ts | 6 +-
.../search_strategy/index_fields/index.ts | 4 +-
.../search_strategy/index_fields/mock.ts | 0
.../timeline/eql/__mocks__/index.ts | 2 +-
.../timeline/eql/helpers.test.ts | 0
.../search_strategy/timeline/eql/helpers.ts | 4 +-
.../search_strategy/timeline/eql/index.ts | 4 +-
.../timeline/factory/events/all/constants.ts | 35 +-
.../factory/events/all/helpers.test.ts | 570 +
.../timeline/factory/events/all/helpers.ts | 7 +-
.../timeline/factory/events/all/index.ts | 30 +-
.../events/all/query.events_all.dsl.ts | 2 +-
.../timeline/factory/events/details/index.ts | 6 +-
.../details/query.events_details.dsl.test.ts | 0
.../details/query.events_details.dsl.ts | 0
.../timeline/factory/events/index.ts | 4 +-
.../timeline/factory/events/kpi/index.ts | 4 +-
.../factory/events/kpi/query.kpi.dsl.ts | 2 +-
.../factory/events/last_event_time/index.ts | 4 +-
.../query.events_last_event_time.dsl.ts | 0
.../search_strategy/timeline/factory/index.ts | 6 +-
.../search_strategy/timeline/factory/types.ts | 2 +-
.../server/search_strategy/timeline/index.ts | 9 +-
x-pack/plugins/timelines/server/types.ts | 13 +-
.../server/utils/beat_schema/fields.ts | 36119 ++++++++++++++++
.../timelines/server/utils/build_query.ts | 21 +
.../plugins/timelines/server/utils/filters.ts | 12 +
x-pack/plugins/timelines/tsconfig.json | 46 +-
.../translations/translations/ja-JP.json | 4 -
.../translations/translations/zh-CN.json | 4 -
.../apis/security_solution/events.ts | 4 +-
.../apis/security_solution/sources.ts | 14 +-
.../security_solution/timeline_details.ts | 4 +-
.../applications/timelines_test/index.tsx | 30 +-
.../plugins/timelines_test/public/plugin.ts | 23 +-
.../test_suites/timelines/index.ts | 2 +-
x-pack/yarn.lock | 31 +
yarn.lock | 30 +-
528 files changed, 60238 insertions(+), 3994 deletions(-)
create mode 100644 packages/kbn-securitysolution-t-grid/BUILD.bazel
create mode 100644 packages/kbn-securitysolution-t-grid/README.md
create mode 100644 packages/kbn-securitysolution-t-grid/babel.config.js
create mode 100644 packages/kbn-securitysolution-t-grid/jest.config.js
create mode 100644 packages/kbn-securitysolution-t-grid/package.json
create mode 100644 packages/kbn-securitysolution-t-grid/react/package.json
create mode 100644 packages/kbn-securitysolution-t-grid/src/constants/index.ts
create mode 100644 packages/kbn-securitysolution-t-grid/src/index.ts
create mode 100644 packages/kbn-securitysolution-t-grid/src/mock/index.ts
rename {x-pack/plugins/security_solution/common/utils => packages/kbn-securitysolution-t-grid/src/mock}/mock_event_details.ts (97%)
create mode 100644 packages/kbn-securitysolution-t-grid/src/utils/api/index.ts
create mode 100644 packages/kbn-securitysolution-t-grid/src/utils/drag_and_drop/index.ts
create mode 100644 packages/kbn-securitysolution-t-grid/src/utils/index.ts
create mode 100644 packages/kbn-securitysolution-t-grid/tsconfig.browser.json
create mode 100644 packages/kbn-securitysolution-t-grid/tsconfig.json
create mode 100644 x-pack/plugins/security_solution/common/types/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/actions/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/cells/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/columns/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/data_provider/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/rows/index.ts
create mode 100644 x-pack/plugins/security_solution/common/types/timeline/store.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/accessibility/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts
delete mode 100644 x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts
create mode 100644 x-pack/plugins/timelines/common/constants.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/agent/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/auditd/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/cloud/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/destination/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/dns/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.test.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/ecs_fields/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/endgame/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/event/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/file/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/geo/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/host/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/http/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/network/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/process/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/ransomware/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/registry/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/rule/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/signal/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/source/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/suricata/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/system/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/threat/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/tls/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/url/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/user/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/winlog/index.ts
create mode 100644 x-pack/plugins/timelines/common/ecs/zeek/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/common/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/eql/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.mock.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.test.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/eql/validation/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/common/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts
create mode 100644 x-pack/plugins/timelines/common/search_strategy/timeline/index.ts
create mode 100644 x-pack/plugins/timelines/common/typed_json.ts
create mode 100644 x-pack/plugins/timelines/common/types/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/actions/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/cells/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/columns/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/note/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/pinned_event/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/rows/index.ts
create mode 100644 x-pack/plugins/timelines/common/types/timeline/store.ts
create mode 100644 x-pack/plugins/timelines/common/utility_types.ts
rename x-pack/plugins/{security_solution/public/common/components => timelines/common/utils}/accessibility/helpers.test.tsx (100%)
rename x-pack/plugins/{security_solution/public/common/components => timelines/common/utils}/accessibility/helpers.ts (99%)
create mode 100644 x-pack/plugins/timelines/common/utils/accessibility/index.ts
rename x-pack/plugins/{security_solution/public/common/utils/api/index.ts => timelines/common/utils/api.ts} (100%)
create mode 100644 x-pack/plugins/timelines/common/utils/field_formatters.test.ts
create mode 100644 x-pack/plugins/timelines/common/utils/field_formatters.ts
create mode 100644 x-pack/plugins/timelines/common/utils/to_array.ts
create mode 100644 x-pack/plugins/timelines/jest.config.js
rename x-pack/plugins/{security_solution/public/common => timelines/public}/components/drag_and_drop/draggable_keyboard_wrapper_hook/index.tsx (92%)
create mode 100644 x-pack/plugins/timelines/public/components/drag_and_drop/helpers.ts
create mode 100644 x-pack/plugins/timelines/public/components/drag_and_drop/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/draggables/field_badge/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/draggables/field_badge/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/draggables/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/exit_full_screen/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/exit_full_screen/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/exit_full_screen/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/inspect/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/inspect/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/inspect/modal.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/inspect/modal.tsx
create mode 100644 x-pack/plugins/timelines/public/components/inspect/translations.ts
rename x-pack/plugins/{security_solution/public/common => timelines/public}/components/last_updated/index.test.tsx (100%)
rename x-pack/plugins/{security_solution/public/common => timelines/public}/components/last_updated/index.tsx (94%)
rename x-pack/plugins/{security_solution/public/common => timelines/public}/components/last_updated/translations.ts (67%)
create mode 100644 x-pack/plugins/timelines/public/components/loading/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/actions/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/column_header.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/common/dragging_container.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/common/styles.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/header_content.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/helpers.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header_tooltip_content/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header_tooltip_content/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/column_headers/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/constants.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/stateful_cell.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/stateful_cell.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/data_driven_columns/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/event_column_view.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/event_column_view.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event_context.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/events/use_stateful_event_focus/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/__snapshots__/plain_row_renderer.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/get_column_renderer.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/get_row_renderer.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/renderers/row_renderer.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/sort/__snapshots__/sort_indicator.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/sort/index.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/sort/sort_indicator.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/sort/sort_indicator.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/sort/sort_number.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/body/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/footer/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/footer/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/footer/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/header_section/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/header_section/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/header_section/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/helpers.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/integrated/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/standalone/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/styles.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/subtitle/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/subtitle/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/subtitle/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/translations.ts
create mode 100644 x-pack/plugins/timelines/public/components/t_grid/types.ts
create mode 100644 x-pack/plugins/timelines/public/components/tgrid.tsx
create mode 100644 x-pack/plugins/timelines/public/components/truncatable_text/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/timelines/public/components/truncatable_text/index.test.tsx
create mode 100644 x-pack/plugins/timelines/public/components/truncatable_text/index.tsx
create mode 100644 x-pack/plugins/timelines/public/components/utils/helpers.ts
create mode 100644 x-pack/plugins/timelines/public/components/utils/keury/index.test.ts
create mode 100644 x-pack/plugins/timelines/public/components/utils/keury/index.ts
create mode 100644 x-pack/plugins/timelines/public/components/utils/use_mount_appended.ts
create mode 100644 x-pack/plugins/timelines/public/container/index.tsx
create mode 100644 x-pack/plugins/timelines/public/container/translations.ts
rename x-pack/plugins/{security_solution/public/common/hooks/use_add_to_timeline.tsx => timelines/public/hooks/use_add_to_timeline.ts} (90%)
create mode 100644 x-pack/plugins/timelines/public/hooks/use_app_toasts.ts
create mode 100644 x-pack/plugins/timelines/public/hooks/use_selector.tsx
delete mode 100644 x-pack/plugins/timelines/public/index.scss
create mode 100644 x-pack/plugins/timelines/public/mock/browser_fields.ts
create mode 100644 x-pack/plugins/timelines/public/mock/cell_renderer.tsx
create mode 100644 x-pack/plugins/timelines/public/mock/global_state.ts
create mode 100644 x-pack/plugins/timelines/public/mock/header.ts
create mode 100644 x-pack/plugins/timelines/public/mock/index.ts
create mode 100644 x-pack/plugins/timelines/public/mock/index_pattern.ts
create mode 100644 x-pack/plugins/timelines/public/mock/kibana_react.mock.ts
create mode 100644 x-pack/plugins/timelines/public/mock/mock_and_providers.tsx
create mode 100644 x-pack/plugins/timelines/public/mock/mock_data_providers.tsx
create mode 100644 x-pack/plugins/timelines/public/mock/mock_local_storage.ts
create mode 100644 x-pack/plugins/timelines/public/mock/mock_timeline_control_columns.tsx
create mode 100644 x-pack/plugins/timelines/public/mock/mock_timeline_data.ts
create mode 100644 x-pack/plugins/timelines/public/mock/plugin_mock.tsx
create mode 100644 x-pack/plugins/timelines/public/mock/test_providers.tsx
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/actions.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/defaults.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/helpers.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/index.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/inputs.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/model.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/reducer.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/selectors.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/translations.ts
create mode 100644 x-pack/plugins/timelines/public/store/t_grid/types.ts
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/index_fields/index.test.ts (99%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/index_fields/index.ts (99%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/index_fields/mock.ts (100%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/eql/__mocks__/index.ts (99%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/eql/helpers.test.ts (100%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/eql/helpers.ts (96%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/eql/index.ts (91%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/all/constants.ts (78%)
create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/all/helpers.ts (96%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/all/index.ts (70%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts (96%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/details/index.ts (89%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.test.ts (100%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts (100%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/index.ts (87%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/kpi/index.ts (90%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts (96%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/last_event_time/index.ts (89%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.ts (100%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/index.ts (72%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/factory/types.ts (88%)
rename x-pack/plugins/{security_solution => timelines}/server/search_strategy/timeline/index.ts (81%)
create mode 100644 x-pack/plugins/timelines/server/utils/beat_schema/fields.ts
create mode 100644 x-pack/plugins/timelines/server/utils/build_query.ts
create mode 100644 x-pack/plugins/timelines/server/utils/filters.ts
create mode 100644 x-pack/yarn.lock
diff --git a/.eslintrc.js b/.eslintrc.js
index 40dd6a55a2a3f..c64f03a8398e5 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -893,6 +893,8 @@ module.exports = {
files: [
'x-pack/plugins/security_solution/public/**/*.{js,mjs,ts,tsx}',
'x-pack/plugins/security_solution/common/**/*.{js,mjs,ts,tsx}',
+ 'x-pack/plugins/timelines/public/**/*.{js,mjs,ts,tsx}',
+ 'x-pack/plugins/timelines/common/**/*.{js,mjs,ts,tsx}',
],
rules: {
'import/no-nodejs-modules': 'error',
@@ -907,7 +909,10 @@ module.exports = {
},
{
// typescript only for front and back end
- files: ['x-pack/plugins/security_solution/**/*.{ts,tsx}'],
+ files: [
+ 'x-pack/plugins/security_solution/**/*.{ts,tsx}',
+ 'x-pack/plugins/timelines/**/*.{ts,tsx}',
+ ],
rules: {
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-explicit-any': 'error',
@@ -917,7 +922,10 @@ module.exports = {
},
{
// typescript and javascript for front and back end
- files: ['x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}'],
+ files: [
+ 'x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}',
+ 'x-pack/plugins/timelines/**/*.{js,mjs,ts,tsx}',
+ ],
plugins: ['eslint-plugin-node', 'react'],
env: {
jest: true,
diff --git a/package.json b/package.json
index 36fa086657adf..9fc62dd69f1cf 100644
--- a/package.json
+++ b/package.json
@@ -149,6 +149,7 @@
"@kbn/securitysolution-list-api": "link:bazel-bin/packages/kbn-securitysolution-list-api",
"@kbn/securitysolution-list-hooks": "link:bazel-bin/packages/kbn-securitysolution-list-hooks",
"@kbn/securitysolution-list-utils": "link:bazel-bin/packages/kbn-securitysolution-list-utils",
+ "@kbn/securitysolution-t-grid": "link:bazel-bin/packages/kbn-securitysolution-t-grid",
"@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils",
"@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools",
"@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository",
@@ -217,6 +218,8 @@
"cytoscape-dagre": "^2.2.2",
"d3": "3.5.17",
"d3-array": "1.2.4",
+ "d3-cloud": "1.2.5",
+ "d3-interpolate": "^3.0.1",
"d3-scale": "1.0.7",
"d3-shape": "^1.1.0",
"d3-time": "^1.1.0",
@@ -511,6 +514,7 @@
"@types/cytoscape": "^3.14.0",
"@types/d3": "^3.5.43",
"@types/d3-array": "^1.2.7",
+ "@types/d3-interpolate": "^2.0.0",
"@types/d3-scale": "^2.1.1",
"@types/d3-shape": "^1.3.1",
"@types/d3-time": "^1.0.10",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index b1c3f580c6baf..801f7cdd7f8dc 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -3,7 +3,7 @@
filegroup(
name = "build",
srcs = [
- "//packages/elastic-datemath:build",
+ "//packages/elastic-datemath:build",
"//packages/elastic-eslint-config-kibana:build",
"//packages/elastic-safer-lodash-set:build",
"//packages/kbn-ace:build",
@@ -41,6 +41,7 @@ filegroup(
"//packages/kbn-securitysolution-list-utils:build",
"//packages/kbn-securitysolution-utils:build",
"//packages/kbn-securitysolution-es-utils:build",
+ "//packages/kbn-securitysolution-t-grid:build",
"//packages/kbn-securitysolution-hook-utils:build",
"//packages/kbn-server-http-tools:build",
"//packages/kbn-server-route-repository:build",
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index f9127e4629f43..c6960621359c7 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -67,7 +67,7 @@ pageLoadAssetSize:
searchprofiler: 67080
security: 95864
securityOss: 30806
- securitySolution: 76000
+ securitySolution: 217673
share: 99061
snapshotRestore: 79032
spaces: 57868
@@ -107,7 +107,7 @@ pageLoadAssetSize:
dataVisualizer: 27530
banners: 17946
mapsEms: 26072
- timelines: 28613
+ timelines: 230410
screenshotMode: 17856
visTypePie: 35583
cases: 144442
diff --git a/packages/kbn-securitysolution-t-grid/BUILD.bazel b/packages/kbn-securitysolution-t-grid/BUILD.bazel
new file mode 100644
index 0000000000000..5cf1081bdd32e
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/BUILD.bazel
@@ -0,0 +1,125 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-securitysolution-t-grid"
+
+PKG_REQUIRE_NAME = "@kbn/securitysolution-t-grid"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ ],
+ exclude = [
+ "**/*.test.*",
+ "**/*.mock.*",
+ ],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "react/package.json",
+ "package.json",
+ "README.md",
+]
+
+SRC_DEPS = [
+ "//packages/kbn-babel-preset",
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-i18n",
+ "@npm//@babel/core",
+ "@npm//babel-loader",
+ "@npm//enzyme",
+ "@npm//jest",
+ "@npm//lodash",
+ "@npm//react",
+ "@npm//react-beautiful-dnd",
+ "@npm//tslib",
+]
+
+TYPES_DEPS = [
+ "@npm//typescript",
+ "@npm//@types/enzyme",
+ "@npm//@types/jest",
+ "@npm//@types/lodash",
+ "@npm//@types/node",
+ "@npm//@types/react",
+ "@npm//@types/react-beautiful-dnd",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_config(
+ name = "tsconfig_browser",
+ src = "tsconfig.browser.json",
+ deps = [
+ "//:tsconfig.base.json",
+ "//:tsconfig.browser.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ["--pretty"],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_dir = "target_types",
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target_node",
+ root_dir = "src",
+ source_map = True,
+ tsconfig = ":tsconfig",
+)
+
+ts_project(
+ name = "tsc_browser",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ allow_js = True,
+ declaration = False,
+ incremental = True,
+ out_dir = "target_web",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig_browser",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ package_name = PKG_REQUIRE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ visibility = ["//visibility:public"],
+ deps = [":tsc", ":tsc_browser"] + DEPS,
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ],
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-securitysolution-t-grid/README.md b/packages/kbn-securitysolution-t-grid/README.md
new file mode 100644
index 0000000000000..a49669c81689a
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/README.md
@@ -0,0 +1,3 @@
+# kbn-securitysolution-t-grid
+
+We do not want to create circular dependencies between security_solution and timelines plugins. Therefore , we will use this packages to share components between these two plugins.
diff --git a/packages/kbn-securitysolution-t-grid/babel.config.js b/packages/kbn-securitysolution-t-grid/babel.config.js
new file mode 100644
index 0000000000000..b4a118df51af5
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/babel.config.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+module.exports = {
+ env: {
+ web: {
+ presets: ['@kbn/babel-preset/webpack_preset'],
+ },
+ node: {
+ presets: ['@kbn/babel-preset/node_preset'],
+ },
+ },
+ ignore: ['**/*.test.ts', '**/*.test.tsx'],
+};
diff --git a/packages/kbn-securitysolution-t-grid/jest.config.js b/packages/kbn-securitysolution-t-grid/jest.config.js
new file mode 100644
index 0000000000000..21e7d2d71b61a
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/jest.config.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../..',
+ roots: ['/packages/kbn-securitysolution-t-grid'],
+};
diff --git a/packages/kbn-securitysolution-t-grid/package.json b/packages/kbn-securitysolution-t-grid/package.json
new file mode 100644
index 0000000000000..68d3a8c71e7ca
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@kbn/securitysolution-t-grid",
+ "version": "1.0.0",
+ "description": "security solution t-grid packages will allow sharing components between timelines and security_solution plugin until we transfer all functionality to timelines plugin",
+ "license": "SSPL-1.0 OR Elastic License 2.0",
+ "browser": "./target_web/browser.js",
+ "main": "./target_node/index.js",
+ "types": "./target_types/index.d.ts",
+ "private": true
+}
diff --git a/packages/kbn-securitysolution-t-grid/react/package.json b/packages/kbn-securitysolution-t-grid/react/package.json
new file mode 100644
index 0000000000000..c29ddd45f084d
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/react/package.json
@@ -0,0 +1,5 @@
+{
+ "browser": "../target_web/react",
+ "main": "../target_node/react",
+ "types": "../target_types/react/index.d.ts"
+}
\ No newline at end of file
diff --git a/packages/kbn-securitysolution-t-grid/src/constants/index.ts b/packages/kbn-securitysolution-t-grid/src/constants/index.ts
new file mode 100644
index 0000000000000..c03c0093d9839
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/constants/index.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
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const HIGHLIGHTED_DROP_TARGET_CLASS_NAME = 'highlighted-drop-target';
+export const EMPTY_PROVIDERS_GROUP_CLASS_NAME = 'empty-providers-group';
+
+/** The draggable will move this many pixels via the keyboard when the arrow key is pressed */
+export const KEYBOARD_DRAG_OFFSET = 20;
+
+export const DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME = 'draggable-keyboard-wrapper';
+
+export const ROW_RENDERER_CLASS_NAME = 'row-renderer';
+
+export const NOTES_CONTAINER_CLASS_NAME = 'notes-container';
+
+export const NOTE_CONTENT_CLASS_NAME = 'note-content';
+
+/** This class is added to the document body while dragging */
+export const IS_DRAGGING_CLASS_NAME = 'is-dragging';
+
+export const HOVER_ACTIONS_ALWAYS_SHOW_CLASS_NAME = 'hover-actions-always-show';
diff --git a/packages/kbn-securitysolution-t-grid/src/index.ts b/packages/kbn-securitysolution-t-grid/src/index.ts
new file mode 100644
index 0000000000000..0c2e9a7dbea8b
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export * from './constants';
+export * from './utils';
+export * from './mock';
diff --git a/packages/kbn-securitysolution-t-grid/src/mock/index.ts b/packages/kbn-securitysolution-t-grid/src/mock/index.ts
new file mode 100644
index 0000000000000..dc1b63dfc33b0
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/mock/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export * from './mock_event_details';
diff --git a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts b/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts
similarity index 97%
rename from x-pack/plugins/security_solution/common/utils/mock_event_details.ts
rename to packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts
index 7dc257ebb3fef..167fc9dd17a2a 100644
--- a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts
+++ b/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
export const eventHit = {
diff --git a/packages/kbn-securitysolution-t-grid/src/utils/api/index.ts b/packages/kbn-securitysolution-t-grid/src/utils/api/index.ts
new file mode 100644
index 0000000000000..34e448419693b
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/utils/api/index.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { has } from 'lodash/fp';
+
+export interface AppError extends Error {
+ body: {
+ message: string;
+ };
+}
+
+export interface KibanaError extends AppError {
+ body: {
+ message: string;
+ statusCode: number;
+ };
+}
+
+export interface SecurityAppError extends AppError {
+ body: {
+ message: string;
+ status_code: number;
+ };
+}
+
+export const isKibanaError = (error: unknown): error is KibanaError =>
+ has('message', error) && has('body.message', error) && has('body.statusCode', error);
+
+export const isSecurityAppError = (error: unknown): error is SecurityAppError =>
+ has('message', error) && has('body.message', error) && has('body.status_code', error);
+
+export const isAppError = (error: unknown): error is AppError =>
+ isKibanaError(error) || isSecurityAppError(error);
+
+export const isNotFoundError = (error: unknown) =>
+ (isKibanaError(error) && error.body.statusCode === 404) ||
+ (isSecurityAppError(error) && error.body.status_code === 404);
diff --git a/packages/kbn-securitysolution-t-grid/src/utils/drag_and_drop/index.ts b/packages/kbn-securitysolution-t-grid/src/utils/drag_and_drop/index.ts
new file mode 100644
index 0000000000000..91b2e88d97358
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/utils/drag_and_drop/index.ts
@@ -0,0 +1,133 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { DropResult } from 'react-beautiful-dnd';
+
+export const draggableIdPrefix = 'draggableId';
+
+export const droppableIdPrefix = 'droppableId';
+
+export const draggableContentPrefix = `${draggableIdPrefix}.content.`;
+
+export const draggableTimelineProvidersPrefix = `${draggableIdPrefix}.timelineProviders.`;
+
+export const draggableFieldPrefix = `${draggableIdPrefix}.field.`;
+
+export const droppableContentPrefix = `${droppableIdPrefix}.content.`;
+
+export const droppableFieldPrefix = `${droppableIdPrefix}.field.`;
+
+export const droppableTimelineProvidersPrefix = `${droppableIdPrefix}.timelineProviders.`;
+
+export const droppableTimelineColumnsPrefix = `${droppableIdPrefix}.timelineColumns.`;
+
+export const droppableTimelineFlyoutBottomBarPrefix = `${droppableIdPrefix}.flyoutButton.`;
+
+export const getDraggableId = (dataProviderId: string): string =>
+ `${draggableContentPrefix}${dataProviderId}`;
+
+export const getDraggableFieldId = ({
+ contextId,
+ fieldId,
+}: {
+ contextId: string;
+ fieldId: string;
+}): string => `${draggableFieldPrefix}${escapeContextId(contextId)}.${escapeFieldId(fieldId)}`;
+
+export const getTimelineProviderDroppableId = ({
+ groupIndex,
+ timelineId,
+}: {
+ groupIndex: number;
+ timelineId: string;
+}): string => `${droppableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}`;
+
+export const getTimelineProviderDraggableId = ({
+ dataProviderId,
+ groupIndex,
+ timelineId,
+}: {
+ dataProviderId: string;
+ groupIndex: number;
+ timelineId: string;
+}): string =>
+ `${draggableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}.${dataProviderId}`;
+
+export const getDroppableId = (visualizationPlaceholderId: string): string =>
+ `${droppableContentPrefix}${visualizationPlaceholderId}`;
+
+export const sourceIsContent = (result: DropResult): boolean =>
+ result.source.droppableId.startsWith(droppableContentPrefix);
+
+export const sourceAndDestinationAreSameTimelineProviders = (result: DropResult): boolean => {
+ const regex = /^droppableId\.timelineProviders\.(\S+)\./;
+ const sourceMatches = result.source.droppableId.match(regex) || [];
+ const destinationMatches =
+ (result.destination && result.destination.droppableId.match(regex)) || [];
+
+ return (
+ sourceMatches.length >= 2 &&
+ destinationMatches.length >= 2 &&
+ sourceMatches[1] === destinationMatches[1]
+ );
+};
+
+export const draggableIsContent = (result: DropResult | { draggableId: string }): boolean =>
+ result.draggableId.startsWith(draggableContentPrefix);
+
+export const draggableIsField = (result: DropResult | { draggableId: string }): boolean =>
+ result.draggableId.startsWith(draggableFieldPrefix);
+
+export const reasonIsDrop = (result: DropResult): boolean => result.reason === 'DROP';
+
+export const destinationIsTimelineProviders = (result: DropResult): boolean =>
+ result.destination != null &&
+ result.destination.droppableId.startsWith(droppableTimelineProvidersPrefix);
+
+export const destinationIsTimelineColumns = (result: DropResult): boolean =>
+ result.destination != null &&
+ result.destination.droppableId.startsWith(droppableTimelineColumnsPrefix);
+
+export const destinationIsTimelineButton = (result: DropResult): boolean =>
+ result.destination != null &&
+ result.destination.droppableId.startsWith(droppableTimelineFlyoutBottomBarPrefix);
+
+export const getProviderIdFromDraggable = (result: DropResult): string =>
+ result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1);
+
+export const getFieldIdFromDraggable = (result: DropResult): string =>
+ unEscapeFieldId(result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1));
+
+export const escapeDataProviderId = (path: string) => path.replace(/\./g, '_');
+
+export const escapeContextId = (path: string) => path.replace(/\./g, '_');
+
+export const escapeFieldId = (path: string) => path.replace(/\./g, '!!!DOT!!!');
+
+export const unEscapeFieldId = (path: string) => path.replace(/!!!DOT!!!/g, '.');
+
+export const providerWasDroppedOnTimeline = (result: DropResult): boolean =>
+ reasonIsDrop(result) &&
+ draggableIsContent(result) &&
+ sourceIsContent(result) &&
+ destinationIsTimelineProviders(result);
+
+export const userIsReArrangingProviders = (result: DropResult): boolean =>
+ reasonIsDrop(result) && sourceAndDestinationAreSameTimelineProviders(result);
+
+export const fieldWasDroppedOnTimelineColumns = (result: DropResult): boolean =>
+ reasonIsDrop(result) && draggableIsField(result) && destinationIsTimelineColumns(result);
+
+/**
+ * Prevents fields from being dragged or dropped to any area other than column
+ * header drop zone in the timeline
+ */
+export const DRAG_TYPE_FIELD = 'drag-type-field';
+
+/** This class is added to the document body while timeline field dragging */
+export const IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME = 'is-timeline-field-dragging';
diff --git a/packages/kbn-securitysolution-t-grid/src/utils/index.ts b/packages/kbn-securitysolution-t-grid/src/utils/index.ts
new file mode 100644
index 0000000000000..39629a990c539
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/src/utils/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export * from './api';
+export * from './drag_and_drop';
diff --git a/packages/kbn-securitysolution-t-grid/tsconfig.browser.json b/packages/kbn-securitysolution-t-grid/tsconfig.browser.json
new file mode 100644
index 0000000000000..a5183ba4fd457
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/tsconfig.browser.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../tsconfig.browser.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "incremental": true,
+ "outDir": "./target_web",
+ "declaration": false,
+ "isolatedModules": true,
+ "sourceMap": true,
+ "sourceRoot": "../../../../../packages/kbn-securitysolution-t-grid/src",
+ "types": [
+ "jest",
+ "node"
+ ],
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ ],
+ "exclude": [
+ "**/__fixtures__/**/*"
+ ]
+}
diff --git a/packages/kbn-securitysolution-t-grid/tsconfig.json b/packages/kbn-securitysolution-t-grid/tsconfig.json
new file mode 100644
index 0000000000000..8cda578edede4
--- /dev/null
+++ b/packages/kbn-securitysolution-t-grid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "incremental": true,
+ "outDir": "target",
+ "rootDir": "src",
+ "sourceMap": true,
+ "sourceRoot": "../../../../packages/kbn-securitysolution-t-grid/src",
+ "types": [
+ "jest",
+ "node"
+ ]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js
index 225f93d487823..5baff607704c7 100644
--- a/packages/kbn-test/jest-preset.js
+++ b/packages/kbn-test/jest-preset.js
@@ -94,7 +94,7 @@ module.exports = {
transformIgnorePatterns: [
// ignore all node_modules except monaco-editor and react-monaco-editor which requires babel transforms to handle dynamic import()
// since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842)
- '[/\\\\]node_modules(?)[/\\\\].+\\.js$',
+ '[/\\\\]node_modules(?)[/\\\\].+\\.js$',
'packages/kbn-pm/dist/index.js',
],
diff --git a/tsconfig.json b/tsconfig.json
index c91f7b768a5c4..f6df8fcbb6406 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -70,7 +70,6 @@
{ "path": "./src/plugins/visualize/tsconfig.json" },
{ "path": "./src/plugins/index_pattern_management/tsconfig.json" },
{ "path": "./src/plugins/index_pattern_field_editor/tsconfig.json" },
-
{ "path": "./x-pack/plugins/actions/tsconfig.json" },
{ "path": "./x-pack/plugins/alerting/tsconfig.json" },
{ "path": "./x-pack/plugins/apm/tsconfig.json" },
diff --git a/tsconfig.refs.json b/tsconfig.refs.json
index 3baf5c323ef81..e08b50cc055c1 100644
--- a/tsconfig.refs.json
+++ b/tsconfig.refs.json
@@ -105,6 +105,7 @@
{ "path": "./x-pack/plugins/stack_alerts/tsconfig.json" },
{ "path": "./x-pack/plugins/task_manager/tsconfig.json" },
{ "path": "./x-pack/plugins/telemetry_collection_xpack/tsconfig.json" },
+ { "path": "./x-pack/plugins/timelines/tsconfig.json" },
{ "path": "./x-pack/plugins/transform/tsconfig.json" },
{ "path": "./x-pack/plugins/translations/tsconfig.json" },
{ "path": "./x-pack/plugins/triggers_actions_ui/tsconfig.json" },
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index b20b1501eecc5..a9a81aa285af7 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -15,6 +15,7 @@ const allowedExperimentalValues = Object.freeze({
trustedAppsByPolicyEnabled: false,
metricsEntitiesEnabled: false,
ruleRegistryEnabled: false,
+ tGridEnabled: false,
});
type ExperimentalConfigKeys = Array;
diff --git a/x-pack/plugins/security_solution/common/index.ts b/x-pack/plugins/security_solution/common/index.ts
index 1fec1c76430eb..e6d7bcc9bd506 100644
--- a/x-pack/plugins/security_solution/common/index.ts
+++ b/x-pack/plugins/security_solution/common/index.ts
@@ -4,3 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
+export * from './types';
+export * from './search_strategy';
+export * from './utility_types';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
index 4fcfbdac3c1b4..095ba4ca20afc 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
@@ -4,52 +4,27 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import type { estypes } from '@elastic/elasticsearch';
import { IEsSearchResponse } from '../../../../../../src/plugins/data/common';
+export type {
+ Inspect,
+ SortField,
+ TimerangeInput,
+ PaginationInputPaginated,
+ DocValueFields,
+ CursorType,
+ TotalValue,
+} from '../../../../timelines/common';
+export { Direction } from '../../../../timelines/common';
export type Maybe = T | null;
export type SearchHit = IEsSearchResponse['rawResponse']['hits']['hits'][0];
-export interface TotalValue {
- value: number;
- relation: string;
-}
-
-export interface Inspect {
- dsl: string[];
-}
-
export interface PageInfoPaginated {
activePage: number;
fakeTotalCount: number;
showMorePagesIndicator: boolean;
}
-
-export interface CursorType {
- value?: Maybe;
- tiebreaker?: Maybe;
-}
-
-export enum Direction {
- asc = 'asc',
- desc = 'desc',
-}
-
-export interface SortField {
- field: Field;
- direction: Direction;
-}
-
-export interface TimerangeInput {
- /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */
- interval: string;
- /** The end of the timerange */
- to: string;
- /** The beginning of the timerange */
- from: string;
-}
-
export interface PaginationInput {
/** The limit parameter allows you to configure the maximum amount of items to be returned */
limit: number;
@@ -59,19 +34,6 @@ export interface PaginationInput {
tiebreaker?: Maybe;
}
-export interface PaginationInputPaginated {
- /** The activePage parameter defines the page of results you want to fetch */
- activePage: number;
- /** The cursorStart parameter defines the start of the results to be displayed */
- cursorStart: number;
- /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */
- fakePossibleCount: number;
- /** The querySize parameter is the number of items to be returned */
- querySize: number;
-}
-
-export type DocValueFields = estypes.SearchDocValueField;
-
export interface Explanation {
value: number;
description: string;
@@ -111,13 +73,3 @@ export interface GenericBuckets {
}
export type StringOrNumber = string | number;
-
-export interface TimerangeFilter {
- range: {
- [timestamp: string]: {
- gte: string;
- lte: string;
- format: string;
- };
- };
-}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index.ts
index 575256b991d16..e3d6736878063 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/index.ts
@@ -8,3 +8,4 @@
export * from './common';
export * from './security_solution';
export * from './timeline';
+export * from './index_fields';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
index d747758640fab..4e5f8af41a2ef 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
@@ -5,37 +5,10 @@
* 2.0.
*/
-import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
-import { Ecs } from '../../../../ecs';
-import { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common';
-import { TimelineRequestOptionsPaginated } from '../..';
-
-export interface TimelineEdges {
- node: TimelineItem;
- cursor: CursorType;
-}
-
-export interface TimelineItem {
- _id: string;
- _index?: Maybe;
- data: TimelineNonEcsData[];
- ecs: Ecs;
-}
-
-export interface TimelineNonEcsData {
- field: string;
- value?: Maybe;
-}
-
-export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
- edges: TimelineEdges[];
- totalCount: number;
- pageInfo: Pick;
- inspect?: Maybe;
-}
-
-export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated {
- fields: string[] | Array<{ field: string; include_unmapped: boolean }>;
- fieldRequested: string[];
- language: 'eql' | 'kuery' | 'lucene';
-}
+export type {
+ TimelineEdges,
+ TimelineItem,
+ TimelineNonEcsData,
+ TimelineEventsAllStrategyResponse,
+ TimelineEventsAllRequestOptions,
+} from '../../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/common/index.ts
index 4a5bd2c99a0eb..e4d2ea52ffdff 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/common/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/common/index.ts
@@ -5,22 +5,8 @@
* 2.0.
*/
-import { Ecs } from '../../../../ecs';
-import { CursorType, Maybe } from '../../../common';
-
-export interface TimelineEdges {
- node: TimelineItem;
- cursor: CursorType;
-}
-
-export interface TimelineItem {
- _id: string;
- _index?: Maybe;
- data: TimelineNonEcsData[];
- ecs: Ecs;
-}
-
-export interface TimelineNonEcsData {
- field: string;
- value?: Maybe;
-}
+export type {
+ TimelineEdges,
+ TimelineItem,
+ TimelineNonEcsData,
+} from '../../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
index 1f9820f8e5c2b..3fd13e56cc7e7 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
@@ -5,27 +5,8 @@
* 2.0.
*/
-import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
-import { Inspect, Maybe } from '../../../common';
-import { TimelineRequestOptionsPaginated } from '../..';
-
-export interface TimelineEventsDetailsItem {
- ariaRowindex?: Maybe;
- category?: string;
- field: string;
- values?: Maybe;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- originalValue?: Maybe;
- isObjectArray: boolean;
-}
-
-export interface TimelineEventsDetailsStrategyResponse extends IEsSearchResponse {
- data?: Maybe;
- inspect?: Maybe;
-}
-
-export interface TimelineEventsDetailsRequestOptions
- extends Partial {
- indexName: string;
- eventId: string;
-}
+export type {
+ TimelineEventsDetailsItem,
+ TimelineEventsDetailsStrategyResponse,
+ TimelineEventsDetailsRequestOptions,
+} from '../../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts
index c508876032fca..10e9bbd7670cd 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts
@@ -5,43 +5,10 @@
* 2.0.
*/
-import { EuiComboBoxOptionOption } from '@elastic/eui';
-import {
- EqlSearchStrategyRequest,
- EqlSearchStrategyResponse,
-} from '../../../../../../../../src/plugins/data/common';
-import { Inspect, Maybe, PaginationInputPaginated } from '../../..';
-import { TimelineEdges, TimelineEventsAllRequestOptions } from '../..';
-import { EqlSearchResponse } from '../../../../detection_engine/types';
-
-export interface TimelineEqlRequestOptions
- extends EqlSearchStrategyRequest,
- Omit {
- eventCategoryField?: string;
- tiebreakerField?: string;
- timestampField?: string;
- size?: number;
-}
-
-export interface TimelineEqlResponse extends EqlSearchStrategyResponse> {
- edges: TimelineEdges[];
- totalCount: number;
- pageInfo: Pick;
- inspect: Maybe;
-}
-
-export interface EqlOptionsData {
- keywordFields: EuiComboBoxOptionOption[];
- dateFields: EuiComboBoxOptionOption[];
- nonDateFields: EuiComboBoxOptionOption[];
-}
-
-export interface EqlOptionsSelected {
- eventCategoryField?: string;
- tiebreakerField?: string;
- timestampField?: string;
- query?: string;
- size?: number;
-}
-
-export type FieldsEqlOptions = keyof EqlOptionsSelected;
+export type {
+ TimelineEqlRequestOptions,
+ TimelineEqlResponse,
+ EqlOptionsData,
+ EqlOptionsSelected,
+ FieldsEqlOptions,
+} from '../../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts
index f29dc4a3c7450..39f23a63c8afe 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts
@@ -5,38 +5,11 @@
* 2.0.
*/
-import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
-import { Inspect, Maybe } from '../../../common';
-import { TimelineRequestBasicOptions } from '../..';
-
-export enum LastEventIndexKey {
- hostDetails = 'hostDetails',
- hosts = 'hosts',
- ipDetails = 'ipDetails',
- network = 'network',
-}
-
-export interface LastTimeDetails {
- hostName?: Maybe;
- ip?: Maybe;
-}
-
-export interface TimelineEventsLastEventTimeStrategyResponse extends IEsSearchResponse {
- lastSeen: Maybe;
- inspect?: Maybe;
-}
-
-export interface TimelineKpiStrategyResponse extends IEsSearchResponse {
- destinationIpCount: number;
- inspect?: Maybe;
- hostCount: number;
- processCount: number;
- sourceIpCount: number;
- userCount: number;
-}
-
-export interface TimelineEventsLastEventTimeRequestOptions
- extends Omit {
- indexKey: LastEventIndexKey;
- details: LastTimeDetails;
-}
+export { LastEventIndexKey } from '../../../../../../timelines/common';
+
+export type {
+ LastTimeDetails,
+ TimelineEventsLastEventTimeStrategyResponse,
+ TimelineKpiStrategyResponse,
+ TimelineEventsLastEventTimeRequestOptions,
+} from '../../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts
index 9c2c23eb334a3..7064ef033fc5a 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts
@@ -24,7 +24,12 @@ import {
SortField,
Maybe,
} from '../common';
-import { DataProviderType, TimelineType, TimelineStatus } from '../../types/timeline';
+import {
+ DataProviderType,
+ TimelineType,
+ TimelineStatus,
+ RowRendererId,
+} from '../../types/timeline';
export * from './events';
@@ -165,25 +170,6 @@ export interface SortTimelineInput {
sortDirection?: Maybe;
}
-export enum RowRendererId {
- alerts = 'alerts',
- auditd = 'auditd',
- auditd_file = 'auditd_file',
- library = 'library',
- netflow = 'netflow',
- plain = 'plain',
- registry = 'registry',
- suricata = 'suricata',
- system = 'system',
- system_dns = 'system_dns',
- system_endgame_process = 'system_endgame_process',
- system_file = 'system_file',
- system_fim = 'system_fim',
- system_security_event = 'system_security_event',
- system_socket = 'system_socket',
- zeek = 'zeek',
-}
-
export interface TimelineInput {
columns?: Maybe;
dataProviders?: Maybe;
diff --git a/x-pack/plugins/security_solution/common/types/index.ts b/x-pack/plugins/security_solution/common/types/index.ts
new file mode 100644
index 0000000000000..9464a33082a49
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './timeline';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/actions/index.ts b/x-pack/plugins/security_solution/common/types/timeline/actions/index.ts
new file mode 100644
index 0000000000000..782af107417c2
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/actions/index.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+export type {
+ ActionProps,
+ HeaderActionProps,
+ GenericActionRowCellRenderProps,
+ HeaderCellRender,
+ RowCellRender,
+ ControlColumnProps,
+} from '../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts b/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts
new file mode 100644
index 0000000000000..83b0ced332a62
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export type { CellValueElementProps } from '../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/columns/index.ts b/x-pack/plugins/security_solution/common/types/timeline/columns/index.ts
new file mode 100644
index 0000000000000..ee4d621e35d6c
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/columns/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export type {
+ ColumnHeaderType,
+ ColumnId,
+ ColumnHeaderOptions,
+ ColumnRenderer,
+} from '../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/data_provider/index.ts b/x-pack/plugins/security_solution/common/types/timeline/data_provider/index.ts
new file mode 100644
index 0000000000000..f363176ac0a88
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/data_provider/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+export { IS_OPERATOR, EXISTS_OPERATOR } from '../../../../../timelines/common';
+
+export type {
+ QueryOperator,
+ DataProviderType,
+ QueryMatch,
+ DataProvider,
+ DataProvidersAnd,
+} from '../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts
index 7ae52a3990ff7..05cf99195774b 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts
@@ -23,6 +23,13 @@ import { FlowTarget } from '../../search_strategy/security_solution/network';
import { errorSchema } from '../../detection_engine/schemas/response/error_schema';
import { Direction, Maybe } from '../../search_strategy';
+export * from './actions';
+export * from './cells';
+export * from './columns';
+export * from './data_provider';
+export * from './rows';
+export * from './store';
+
/*
* ColumnHeader Types
*/
@@ -492,6 +499,11 @@ export type TimelineExpandedDetail = {
[tab in TimelineTabs]?: TimelineExpandedDetailType;
};
+export type ToggleDetailPanel = TimelineExpandedDetailType & {
+ tabType?: TimelineTabs;
+ timelineId: string;
+};
+
export const pageInfoTimeline = runtimeTypes.type({
pageIndex: runtimeTypes.number,
pageSize: runtimeTypes.number,
diff --git a/x-pack/plugins/security_solution/common/types/timeline/rows/index.ts b/x-pack/plugins/security_solution/common/types/timeline/rows/index.ts
new file mode 100644
index 0000000000000..ae2d19a5e2ca8
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/rows/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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+export type { RowRenderer } from '../../../../../timelines/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts
new file mode 100644
index 0000000000000..01fc9db7c8e1d
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ ColumnHeaderOptions,
+ ColumnId,
+ RowRendererId,
+ TimelineExpandedDetail,
+ TimelineTypeLiteral,
+} from '.';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { Filter } from '../../../../../../src/plugins/data/public';
+
+import { Direction } from '../../search_strategy';
+import { DataProvider } from './data_provider';
+
+export type KueryFilterQueryKind = 'kuery' | 'lucene' | 'eql';
+
+export interface KueryFilterQuery {
+ kind: KueryFilterQueryKind;
+ expression: string;
+}
+
+export interface SerializedFilterQuery {
+ kuery: KueryFilterQuery | null;
+ serializedQuery: string;
+}
+
+export type SortDirection = 'none' | 'asc' | 'desc' | Direction;
+export interface SortColumnTimeline {
+ columnId: string;
+ columnType: string;
+ sortDirection: SortDirection;
+}
+
+export interface TimelinePersistInput {
+ id: string;
+ dataProviders?: DataProvider[];
+ dateRange?: {
+ start: string;
+ end: string;
+ };
+ excludedRowRendererIds?: RowRendererId[];
+ expandedDetail?: TimelineExpandedDetail;
+ filters?: Filter[];
+ columns: ColumnHeaderOptions[];
+ itemsPerPage?: number;
+ indexNames: string[];
+ kqlQuery?: {
+ filterQuery: SerializedFilterQuery | null;
+ };
+ show?: boolean;
+ sort?: SortColumnTimeline[];
+ showCheckboxes?: boolean;
+ timelineType?: TimelineTypeLiteral;
+ templateTimelineId?: string | null;
+ templateTimelineVersion?: number | null;
+}
+
+/** Invoked when a column is sorted */
+export type OnColumnSorted = (sorted: { columnId: ColumnId; sortDirection: SortDirection }) => void;
+
+export type OnColumnsSorted = (
+ sorted: Array<{ columnId: ColumnId; sortDirection: SortDirection }>
+) => void;
+
+export type OnColumnRemoved = (columnId: ColumnId) => void;
+
+export type OnColumnResized = ({ columnId, delta }: { columnId: ColumnId; delta: number }) => void;
+
+/** Invoked when a user clicks to load more item */
+export type OnChangePage = (nextPage: number) => void;
+
+/** Invoked when a user checks/un-checks a row */
+export type OnRowSelected = ({
+ eventIds,
+ isSelected,
+}: {
+ eventIds: string[];
+ isSelected: boolean;
+}) => void;
+
+/** Invoked when a user checks/un-checks the select all checkbox */
+export type OnSelectAll = ({ isSelected }: { isSelected: boolean }) => void;
+
+/** Invoked when columns are updated */
+export type OnUpdateColumns = (columns: ColumnHeaderOptions[]) => void;
+
+/** Invoked when a user pins an event */
+export type OnPinEvent = (eventId: string) => void;
+
+/** Invoked when a user unpins an event */
+export type OnUnPinEvent = (eventId: string) => void;
diff --git a/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts b/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts
index b724c0f672b50..64d4f2986903a 100644
--- a/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts
+++ b/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts
@@ -7,7 +7,7 @@
import { EventHit, EventSource } from '../search_strategy';
import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './field_formatters';
-import { eventDetailsFormattedFields, eventHit } from './mock_event_details';
+import { eventDetailsFormattedFields, eventHit } from '@kbn/securitysolution-t-grid';
describe('Events Details Helpers', () => {
const fields: EventHit['fields'] = eventHit.fields;
diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
index 78ee3fdcdcdd5..3ff036fa0107f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
@@ -45,7 +45,7 @@ describe('Overview Page', () => {
describe('with no data', () => {
it('Splash screen should be here', () => {
- cy.stubSearchStrategyApi(emptyInstance, undefined, 'securitySolutionIndexFields');
+ cy.stubSearchStrategyApi(emptyInstance, undefined, 'indexFields');
loginAndWaitForPage(OVERVIEW_URL);
cy.get(OVERVIEW_EMPTY_PAGE).should('be.visible');
});
diff --git a/x-pack/plugins/security_solution/cypress/support/commands.js b/x-pack/plugins/security_solution/cypress/support/commands.js
index 90eb9a38d7509..e74d06cd621fb 100644
--- a/x-pack/plugins/security_solution/cypress/support/commands.js
+++ b/x-pack/plugins/security_solution/cypress/support/commands.js
@@ -35,7 +35,7 @@ Cypress.Commands.add(
'stubSearchStrategyApi',
function (stubObject, factoryQueryType, searchStrategyName = 'securitySolutionSearchStrategy') {
cy.intercept('POST', '/internal/bsearch', (req) => {
- if (searchStrategyName === 'securitySolutionIndexFields') {
+ if (searchStrategyName === 'indexFields') {
req.reply(stubObject.rawResponse);
} else if (factoryQueryType === 'overviewHost') {
req.reply(stubObject.overviewHost);
diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json
index 02dbc56bd3397..e26f0d9b65bfa 100644
--- a/x-pack/plugins/security_solution/kibana.json
+++ b/x-pack/plugins/security_solution/kibana.json
@@ -17,6 +17,7 @@
"inspector",
"licensing",
"maps",
+ "timelines",
"triggersActionsUi",
"uiActions"
],
diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx
index cfb25c4436db3..2dc7f632c8482 100644
--- a/x-pack/plugins/security_solution/public/app/app.tsx
+++ b/x-pack/plugins/security_solution/public/app/app.tsx
@@ -21,7 +21,6 @@ import { GlobalToaster, ManageGlobalToaster } from '../common/components/toaster
import { KibanaContextProvider, useKibana, useUiSetting$ } from '../common/lib/kibana';
import { State } from '../common/store';
-import { ManageGlobalTimeline } from '../timelines/components/manage_timeline';
import { StartServices } from '../types';
import { PageRouter } from './routes';
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
@@ -42,23 +41,21 @@ const StartAppComponent: FC = ({ children, history, onAppLeav
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/index.ts b/x-pack/plugins/security_solution/public/common/components/accessibility/index.ts
new file mode 100644
index 0000000000000..f05644c85e536
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/accessibility/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './tooltip_with_keyboard_shortcut';
diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx
index 97922ecdc5b61..2d66b4e93e4dc 100644
--- a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx
@@ -10,7 +10,7 @@ import React from 'react';
import * as i18n from './translations';
-interface Props {
+export interface TooltipWithKeyboardShortcutProps {
additionalScreenReaderOnlyContext?: string;
content: React.ReactNode;
shortcut: string;
@@ -22,7 +22,7 @@ const TooltipWithKeyboardShortcutComponent = ({
content,
shortcut,
showShortcut,
-}: Props) => (
+}: TooltipWithKeyboardShortcutProps) => (
<>
{content}
{additionalScreenReaderOnlyContext !== '' && (
diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx
index 43d5c66655808..58cca7bcbd121 100644
--- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx
@@ -6,12 +6,12 @@
*/
import React, { useEffect, useMemo } from 'react';
-
+import { useDispatch } from 'react-redux';
+import { timelineActions } from '../../../timelines/store/timeline';
import { Filter } from '../../../../../../../src/plugins/data/public';
import { TimelineIdLiteral } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../events_viewer';
import { alertsDefaultModel } from './default_headers';
-import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import * as i18n from './translations';
@@ -70,22 +70,24 @@ const AlertsTableComponent: React.FC = ({
startDate,
pageFilters = [],
}) => {
+ const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { filterManager } = useKibana().services.data.query;
- const { initializeTimeline } = useManageTimeline();
useEffect(() => {
- initializeTimeline({
- id: timelineId,
- documentType: i18n.ALERTS_DOCUMENT_TYPE,
- filterManager,
- defaultModel: alertsDefaultModel,
- footerText: i18n.TOTAL_COUNT_OF_ALERTS,
- title: i18n.ALERTS_TABLE_TITLE,
- unit: i18n.UNIT,
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ dispatch(
+ timelineActions.initializeTGridSettings({
+ id: timelineId,
+ documentType: i18n.ALERTS_DOCUMENT_TYPE,
+ filterManager,
+ defaultColumns: alertsDefaultModel.columns,
+ excludedRowRendererIds: alertsDefaultModel.excludedRowRendererIds,
+ footerText: i18n.TOTAL_COUNT_OF_ALERTS,
+ title: i18n.ALERTS_TABLE_TITLE,
+ // TODO: avoid passing this through the store
+ })
+ );
+ }, [dispatch, filterManager, timelineId]);
return (
{
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx
index 4958f6bae4a30..175239fcaebe7 100644
--- a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx
@@ -14,6 +14,8 @@ import { TestProviders } from '../../mock';
import { DraggableLegendItem, LegendItem } from './draggable_legend_item';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
index dc0e24fcba8f5..bc3b9c3eaa1c6 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../mock';
import { DragDropContextWrapper } from './drag_drop_context_wrapper';
+jest.mock('../../lib/kibana');
+
describe('DragDropContextWrapper', () => {
describe('rendering', () => {
test('it renders against the snapshot', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx
index 1073ed57dee19..1ab19c44e29b2 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx
@@ -11,6 +11,7 @@ import { DropResult, DragDropContext } from 'react-beautiful-dnd';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import deepEqual from 'fast-deep-equal';
+import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import { BeforeCapture } from './drag_drop_context';
import { BrowserFields } from '../../containers/source';
@@ -23,22 +24,24 @@ import {
ADDED_TO_TIMELINE_MESSAGE,
ADDED_TO_TIMELINE_TEMPLATE_MESSAGE,
} from '../../hooks/translations';
-import { useAddToTimelineSensor } from '../../hooks/use_add_to_timeline';
import { displaySuccessToast, useStateToaster } from '../toasters';
import { TimelineId, TimelineType } from '../../../../common/types/timeline';
import {
- addFieldToTimelineColumns,
addProviderToTimeline,
fieldWasDroppedOnTimelineColumns,
- getTimelineIdFromColumnDroppableId,
- IS_DRAGGING_CLASS_NAME,
IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME,
providerWasDroppedOnTimeline,
draggableIsField,
userIsReArrangingProviders,
} from './helpers';
import { useDeepEqualSelector } from '../../hooks/use_selector';
+import { useKibana } from '../../lib/kibana';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
+import {
+ addFieldToTimelineColumns,
+ getTimelineIdFromColumnDroppableId,
+} from '../../../../../timelines/public';
+import { alertsHeaders } from '../alerts_viewer/default_headers';
// @ts-expect-error
window['__react-beautiful-dnd-disable-dev-warnings'] = true;
@@ -85,6 +88,7 @@ const onDragEndHandler = ({
} else if (fieldWasDroppedOnTimelineColumns(result)) {
addFieldToTimelineColumns({
browserFields,
+ defaultsHeader: alertsHeaders,
dispatch,
result,
timelineId: getTimelineIdFromColumnDroppableId(result.destination?.droppableId ?? ''),
@@ -92,8 +96,6 @@ const onDragEndHandler = ({
}
};
-const sensors = [useAddToTimelineSensor];
-
/**
* DragDropContextWrapperComponent handles all drag end events
*/
@@ -101,7 +103,8 @@ export const DragDropContextWrapperComponent: React.FC = ({ browserFields
const dispatch = useDispatch();
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const getDataProviders = useMemo(() => dragAndDropSelectors.getDataProvidersSelector(), []);
-
+ const { timelines } = useKibana().services;
+ const sensors = [timelines.getUseAddToTimelineSensor()];
const {
dataProviders: activeTimelineDataProviders,
timelineType,
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
index 0d8011ee8b65d..bdc5545880e1c 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
@@ -17,6 +17,8 @@ import { DragDropContextWrapper } from './drag_drop_context_wrapper';
import { ConditionalPortal, DraggableWrapper, getStyle } from './draggable_wrapper';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../../lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
index 0cb030862a389..9db5b3899d8bc 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
@@ -6,6 +6,7 @@
*/
import { EuiScreenReaderOnly } from '@elastic/eui';
+import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import {
Draggable,
@@ -24,12 +25,12 @@ import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/com
import { TruncatableText } from '../truncatable_text';
import { WithHoverActions } from '../with_hover_actions';
-import { useDraggableKeyboardWrapper } from './draggable_keyboard_wrapper_hook';
import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content';
-import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME, getDraggableId, getDroppableId } from './helpers';
+import { getDraggableId, getDroppableId } from './helpers';
import { ProviderContainer } from './provider_container';
import * as i18n from './translations';
+import { useKibana } from '../../lib/kibana';
// As right now, we do not know what we want there, we will keep it as a placeholder
export const DragEffects = styled.div``;
@@ -142,6 +143,7 @@ const DraggableWrapperComponent: React.FC = ({
const isDisabled = dataProvider.id.includes(`-${ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID}-`);
const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState(false);
const dispatch = useDispatch();
+ const { timelines } = useKibana().services;
const handleClosePopOverTrigger = useCallback(() => {
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
@@ -297,7 +299,7 @@ const DraggableWrapperComponent: React.FC = ({
setHoverActionsOwnFocus(true);
}, []);
- const { onBlur, onKeyDown } = useDraggableKeyboardWrapper({
+ const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
closePopover: handleClosePopOverTrigger,
draggableId: getDraggableId(dataProvider.id),
fieldName: dataProvider.queryMatch.field,
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 0d688bd805999..400b178c167f6 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
@@ -17,14 +17,10 @@ import { TestProviders } from '../../mock';
import { FilterManager } from '../../../../../../../src/plugins/data/public';
import { useSourcererScope } from '../../containers/sourcerer';
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
-import {
- ManageGlobalTimeline,
- getTimelineDefaults,
-} from '../../../timelines/components/manage_timeline';
import { TimelineId } from '../../../../common/types/timeline';
+import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
jest.mock('../link_to');
-
jest.mock('../../lib/kibana');
jest.mock('../../containers/sourcerer', () => {
const original = jest.requireActual('../../containers/sourcerer');
@@ -42,29 +38,18 @@ jest.mock('uuid', () => {
};
});
const mockStartDragToTimeline = jest.fn();
-jest.mock('../../hooks/use_add_to_timeline', () => {
- const original = jest.requireActual('../../hooks/use_add_to_timeline');
+jest.mock('../../../../../timelines/public/hooks/use_add_to_timeline', () => {
+ const original = jest.requireActual('../../../../../timelines/public/hooks/use_add_to_timeline');
return {
...original,
useAddToTimeline: () => ({ startDragToTimeline: mockStartDragToTimeline }),
};
});
const mockAddFilters = jest.fn();
-const mockGetTimelineFilterManager = jest.fn().mockReturnValue({
- addFilters: mockAddFilters,
-});
-jest.mock('../../../timelines/components/manage_timeline', () => {
- const original = jest.requireActual('../../../timelines/components/manage_timeline');
-
- return {
- ...original,
- useManageTimeline: () => ({
- getManageTimelineById: jest.fn().mockReturnValue({ indexToAdd: [] }),
- getTimelineFilterManager: mockGetTimelineFilterManager,
- isManagedTimeline: jest.fn().mockReturnValue(false),
- }),
- };
-});
+jest.mock('../../../common/hooks/use_selector', () => ({
+ useShallowEqualSelector: jest.fn(),
+ useDeepEqualSelector: jest.fn(),
+}));
const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
const timelineId = TimelineId.active;
@@ -85,6 +70,9 @@ const defaultProps = {
describe('DraggableWrapperHoverContent', () => {
beforeAll(() => {
mockStartDragToTimeline.mockReset();
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({
+ filterManager: { addFilters: mockAddFilters },
+ });
(useSourcererScope as jest.Mock).mockReturnValue({
browserFields: mockBrowserFields,
selectedPatterns: [],
@@ -144,15 +132,10 @@ describe('DraggableWrapperHoverContent', () => {
beforeEach(() => {
onFilterAdded = jest.fn();
- const manageTimelineForTesting = {
- [timelineId]: getTimelineDefaults(timelineId),
- };
wrapper = mount(
-
-
-
+
);
});
@@ -237,18 +220,9 @@ describe('DraggableWrapperHoverContent', () => {
filterManager.addFilters = jest.fn();
onFilterAdded = jest.fn();
- const manageTimelineForTesting = {
- [timelineId]: {
- ...getTimelineDefaults(timelineId),
- filterManager,
- },
- };
-
wrapper = mount(
-
-
-
+
);
});
@@ -586,39 +560,4 @@ describe('DraggableWrapperHoverContent', () => {
expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(false);
});
});
-
- describe('Filter Manager', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
- test('filter manager, not active timeline', () => {
- mount(
-
-
-
- );
-
- expect(mockGetTimelineFilterManager).not.toBeCalled();
- });
- test('filter manager, active timeline', () => {
- mount(
-
-
-
- );
-
- expect(mockGetTimelineFilterManager).toBeCalled();
- });
- test('filter manager, active timeline in draggableId', () => {
- mount(
-
-
-
- );
-
- expect(mockGetTimelineFilterManager).toBeCalled();
- });
- });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
index 880f0b4e18aca..71c3114015a03 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
@@ -12,14 +12,12 @@ import {
EuiScreenReaderOnly,
EuiToolTip,
} from '@elastic/eui';
+
import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react';
import { DraggableId } from 'react-beautiful-dnd';
import styled from 'styled-components';
-import { stopPropagationAndPreventDefault } from '../accessibility/helpers';
-import { TooltipWithKeyboardShortcut } from '../accessibility/tooltip_with_keyboard_shortcut';
import { getAllFieldsByName } from '../../containers/source';
-import { useAddToTimeline } from '../../hooks/use_add_to_timeline';
import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../lib/clipboard/clipboard';
import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard';
import { useKibana } from '../../lib/kibana';
@@ -28,11 +26,14 @@ import { StatefulTopN } from '../top_n';
import { allowTopN } from './helpers';
import * as i18n from './translations';
-import { useManageTimeline } from '../../../timelines/components/manage_timeline';
+import { useDeepEqualSelector } from '../../hooks/use_selector';
import { TimelineId } from '../../../../common/types/timeline';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { useSourcererScope } from '../../containers/sourcerer';
+import { timelineSelectors } from '../../../timelines/store/timeline';
+import { stopPropagationAndPreventDefault } from '../../../../../timelines/public';
+import { TooltipWithKeyboardShortcut } from '../accessibility';
export const AdditionalContent = styled.div`
padding: 2px;
@@ -102,21 +103,25 @@ const DraggableWrapperHoverContentComponent: React.FC = ({
toggleTopN,
value,
}) => {
- const { startDragToTimeline } = useAddToTimeline({ draggableId, fieldName: field });
const kibana = useKibana();
+ const { timelines } = kibana.services;
+ const { startDragToTimeline } = timelines.getUseAddToTimeline()({
+ draggableId,
+ fieldName: field,
+ });
const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
kibana.services.data.query.filterManager,
]);
- const { getTimelineFilterManager } = useManageTimeline();
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { filterManager: activeFilterMananager } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId ?? '')
+ );
const defaultFocusedButtonRef = useRef(null);
const panelRef = useRef(null);
const filterManager = useMemo(
- () =>
- timelineId === TimelineId.active
- ? getTimelineFilterManager(TimelineId.active)
- : filterManagerBackup,
- [timelineId, getTimelineFilterManager, filterManagerBackup]
+ () => (timelineId === TimelineId.active ? activeFilterMananager : filterManagerBackup),
+ [timelineId, activeFilterMananager, filterManagerBackup]
);
// Regarding data from useManageTimeline:
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
index 42f70e9d296b3..73a732b5d6458 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
@@ -15,6 +15,8 @@ import { DragDropContextWrapper } from './drag_drop_context_wrapper';
import { DroppableWrapper } from './droppable_wrapper';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../../lib/kibana');
+
describe('DroppableWrapper', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts
index 58d2e0e7dc70f..a14a44cd9a68b 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.test.ts
@@ -7,6 +7,7 @@
import { omit } from 'lodash/fp';
import { DropResult } from 'react-beautiful-dnd';
+import { getTimelineIdFromColumnDroppableId } from '../../../../../timelines/public';
import { IdToDataProvider } from '../../store/drag_and_drop/model';
@@ -33,7 +34,6 @@ import {
getDroppableId,
getFieldIdFromDraggable,
getProviderIdFromDraggable,
- getTimelineIdFromColumnDroppableId,
getTimelineProviderDraggableId,
getTimelineProviderDroppableId,
providerWasDroppedOnTimeline,
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts
index e2e506e6e1a3f..9717e1e1eda91 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts
@@ -4,138 +4,53 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
-import { isString } from 'lodash/fp';
-import { DropResult, FluidDragActions, Position } from 'react-beautiful-dnd';
+import { DropResult } from 'react-beautiful-dnd';
import { Dispatch } from 'redux';
import { ActionCreator } from 'typescript-fsa';
+import { getProviderIdFromDraggable } from '@kbn/securitysolution-t-grid';
-import { stopPropagationAndPreventDefault } from '../accessibility/helpers';
-import { alertsHeaders } from '../alerts_viewer/default_headers';
-import { BrowserField, BrowserFields, getAllFieldsByName } from '../../containers/source';
+import { BrowserField } from '../../containers/source';
import { dragAndDropActions } from '../../store/actions';
import { IdToDataProvider } from '../../store/drag_and_drop/model';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
-import { timelineActions } from '../../../timelines/store/timeline';
-import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import { addContentToTimeline } from '../../../timelines/components/timeline/data_providers/helpers';
import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
-import { TimelineId } from '../../../../common/types/timeline';
-
-export const draggableIdPrefix = 'draggableId';
-
-export const droppableIdPrefix = 'droppableId';
-
-export const draggableContentPrefix = `${draggableIdPrefix}.content.`;
-
-export const draggableTimelineProvidersPrefix = `${draggableIdPrefix}.timelineProviders.`;
-
-export const draggableFieldPrefix = `${draggableIdPrefix}.field.`;
-
-export const droppableContentPrefix = `${droppableIdPrefix}.content.`;
-
-export const droppableFieldPrefix = `${droppableIdPrefix}.field.`;
-
-export const droppableTimelineProvidersPrefix = `${droppableIdPrefix}.timelineProviders.`;
-
-export const droppableTimelineColumnsPrefix = `${droppableIdPrefix}.timelineColumns.`;
-
-export const droppableTimelineFlyoutBottomBarPrefix = `${droppableIdPrefix}.flyoutButton.`;
-
-export const getDraggableId = (dataProviderId: string): string =>
- `${draggableContentPrefix}${dataProviderId}`;
-
-export const getDraggableFieldId = ({
- contextId,
- fieldId,
-}: {
- contextId: string;
- fieldId: string;
-}): string => `${draggableFieldPrefix}${escapeContextId(contextId)}.${escapeFieldId(fieldId)}`;
-
-export const getTimelineProviderDroppableId = ({
- groupIndex,
- timelineId,
-}: {
- groupIndex: number;
- timelineId: string;
-}): string => `${droppableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}`;
-
-export const getTimelineProviderDraggableId = ({
- dataProviderId,
- groupIndex,
- timelineId,
-}: {
- dataProviderId: string;
- groupIndex: number;
- timelineId: string;
-}): string =>
- `${draggableTimelineProvidersPrefix}${timelineId}.group.${groupIndex}.${dataProviderId}`;
-
-export const getDroppableId = (visualizationPlaceholderId: string): string =>
- `${droppableContentPrefix}${visualizationPlaceholderId}`;
-
-export const sourceIsContent = (result: DropResult): boolean =>
- result.source.droppableId.startsWith(droppableContentPrefix);
-
-export const sourceAndDestinationAreSameTimelineProviders = (result: DropResult): boolean => {
- const regex = /^droppableId\.timelineProviders\.(\S+)\./;
- const sourceMatches = result.source.droppableId.match(regex) ?? [];
- const destinationMatches = result.destination?.droppableId.match(regex) ?? [];
-
- return (
- sourceMatches.length >= 2 &&
- destinationMatches.length >= 2 &&
- sourceMatches[1] === destinationMatches[1]
- );
-};
-
-export const draggableIsContent = (result: DropResult | { draggableId: string }): boolean =>
- result.draggableId.startsWith(draggableContentPrefix);
-
-export const draggableIsField = (result: DropResult | { draggableId: string }): boolean =>
- result.draggableId.startsWith(draggableFieldPrefix);
-
-export const reasonIsDrop = (result: DropResult): boolean => result.reason === 'DROP';
-
-export const destinationIsTimelineProviders = (result: DropResult): boolean =>
- result.destination != null &&
- result.destination.droppableId.startsWith(droppableTimelineProvidersPrefix);
-
-export const destinationIsTimelineColumns = (result: DropResult): boolean =>
- result.destination != null &&
- result.destination.droppableId.startsWith(droppableTimelineColumnsPrefix);
-
-export const destinationIsTimelineButton = (result: DropResult): boolean =>
- result.destination != null &&
- result.destination.droppableId.startsWith(droppableTimelineFlyoutBottomBarPrefix);
-
-export const getProviderIdFromDraggable = (result: DropResult): string =>
- result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1);
-
-export const getFieldIdFromDraggable = (result: DropResult): string =>
- unEscapeFieldId(result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1));
-
-export const escapeDataProviderId = (path: string) => path.replace(/\./g, '_');
-
-export const escapeContextId = (path: string) => path.replace(/\./g, '_');
-
-export const escapeFieldId = (path: string) => path.replace(/\./g, '!!!DOT!!!');
-
-export const unEscapeFieldId = (path: string) => path.replace(/!!!DOT!!!/g, '.');
-
-export const providerWasDroppedOnTimeline = (result: DropResult): boolean =>
- reasonIsDrop(result) &&
- draggableIsContent(result) &&
- sourceIsContent(result) &&
- destinationIsTimelineProviders(result);
-
-export const userIsReArrangingProviders = (result: DropResult): boolean =>
- reasonIsDrop(result) && sourceAndDestinationAreSameTimelineProviders(result);
-
-export const fieldWasDroppedOnTimelineColumns = (result: DropResult): boolean =>
- reasonIsDrop(result) && draggableIsField(result) && destinationIsTimelineColumns(result);
+export {
+ draggableIdPrefix,
+ droppableIdPrefix,
+ draggableContentPrefix,
+ draggableTimelineProvidersPrefix,
+ draggableFieldPrefix,
+ draggableIsField,
+ droppableContentPrefix,
+ droppableFieldPrefix,
+ droppableTimelineProvidersPrefix,
+ droppableTimelineColumnsPrefix,
+ droppableTimelineFlyoutBottomBarPrefix,
+ getDraggableId,
+ getDraggableFieldId,
+ getTimelineProviderDroppableId,
+ getTimelineProviderDraggableId,
+ getDroppableId,
+ sourceIsContent,
+ sourceAndDestinationAreSameTimelineProviders,
+ draggableIsContent,
+ reasonIsDrop,
+ destinationIsTimelineProviders,
+ destinationIsTimelineColumns,
+ destinationIsTimelineButton,
+ getProviderIdFromDraggable,
+ getFieldIdFromDraggable,
+ escapeDataProviderId,
+ escapeContextId,
+ escapeFieldId,
+ unEscapeFieldId,
+ providerWasDroppedOnTimeline,
+ userIsReArrangingProviders,
+ fieldWasDroppedOnTimelineColumns,
+ DRAG_TYPE_FIELD,
+ IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME,
+} from '@kbn/securitysolution-t-grid';
interface AddProviderToTimelineParams {
activeTimelineDataProviders: DataProvider[];
dataProviders: IdToDataProvider;
@@ -148,18 +63,6 @@ interface AddProviderToTimelineParams {
timelineId: string;
}
-interface AddFieldToTimelineColumnsParams {
- upsertColumn?: ActionCreator<{
- column: ColumnHeaderOptions;
- id: string;
- index: number;
- }>;
- browserFields: BrowserFields;
- dispatch: Dispatch;
- result: DropResult;
- timelineId: string;
-}
-
export const addProviderToTimeline = ({
activeTimelineDataProviders,
dataProviders,
@@ -186,73 +89,6 @@ export const addProviderToTimeline = ({
}
};
-const linkFields: Record = {
- 'signal.rule.name': 'signal.rule.id',
- 'event.module': 'rule.reference',
-};
-
-export const addFieldToTimelineColumns = ({
- upsertColumn = timelineActions.upsertColumn,
- browserFields,
- dispatch,
- result,
- timelineId,
-}: AddFieldToTimelineColumnsParams): void => {
- const fieldId = getFieldIdFromDraggable(result);
- const allColumns = getAllFieldsByName(browserFields);
- const column = allColumns[fieldId];
- const initColumnHeader =
- timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage
- ? alertsHeaders.find((c) => c.id === fieldId) ?? {}
- : {};
-
- if (column != null) {
- dispatch(
- upsertColumn({
- column: {
- category: column.category,
- columnHeaderType: 'not-filtered',
- description: isString(column.description) ? column.description : undefined,
- example: isString(column.example) ? column.example : undefined,
- id: fieldId,
- linkField: linkFields[fieldId] ?? undefined,
- type: column.type,
- aggregatable: column.aggregatable,
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- ...initColumnHeader,
- },
- id: timelineId,
- index: result.destination != null ? result.destination.index : 0,
- })
- );
- } else {
- // create a column definition, because it doesn't exist in the browserFields:
- dispatch(
- upsertColumn({
- column: {
- columnHeaderType: 'not-filtered',
- id: fieldId,
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- id: timelineId,
- index: result.destination != null ? result.destination.index : 0,
- })
- );
- }
-};
-
-/**
- * Prevents fields from being dragged or dropped to any area other than column
- * header drop zone in the timeline
- */
-export const DRAG_TYPE_FIELD = 'drag-type-field';
-
-/** This class is added to the document body while dragging */
-export const IS_DRAGGING_CLASS_NAME = 'is-dragging';
-
-/** This class is added to the document body while timeline field dragging */
-export const IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME = 'is-timeline-field-dragging';
-
export const allowTopN = ({
browserField,
fieldName,
@@ -347,125 +183,3 @@ export const allowTopN = ({
return isAllowlistedNonBrowserField || (isAggregatable && isAllowedType);
};
-
-export const getTimelineIdFromColumnDroppableId = (droppableId: string) =>
- droppableId.slice(droppableId.lastIndexOf('.') + 1);
-
-/** The draggable will move this many pixes via the keyboard when the arrow key is pressed */
-export const KEYBOARD_DRAG_OFFSET = 20;
-
-export const DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME = 'draggable-keyboard-wrapper';
-
-/**
- * Temporarily disables tab focus on child links of the draggable to work
- * around an issue where tab focus becomes stuck on the interactive children
- *
- * NOTE: This function is (intentionally) only effective when used in a key
- * event handler, because it automatically restores focus capabilities on
- * the next tick.
- */
-export const temporarilyDisableInteractiveChildTabIndexes = (draggableElement: HTMLDivElement) => {
- const interactiveChildren = draggableElement.querySelectorAll('a, button');
- interactiveChildren.forEach((interactiveChild) => {
- interactiveChild.setAttribute('tabindex', '-1'); // DOM mutation
- });
-
- // restore the default tabindexs on the next tick:
- setTimeout(() => {
- interactiveChildren.forEach((interactiveChild) => {
- interactiveChild.setAttribute('tabindex', '0'); // DOM mutation
- });
- }, 0);
-};
-
-export const draggableKeyDownHandler = ({
- beginDrag,
- cancelDragActions,
- closePopover,
- draggableElement,
- dragActions,
- dragToLocation,
- endDrag,
- keyboardEvent,
- openPopover,
- setDragActions,
-}: {
- beginDrag: () => FluidDragActions | null;
- cancelDragActions: () => void;
- closePopover?: () => void;
- draggableElement: HTMLDivElement;
- dragActions: FluidDragActions | null;
- dragToLocation: ({
- // eslint-disable-next-line @typescript-eslint/no-shadow
- dragActions,
- position,
- }: {
- dragActions: FluidDragActions | null;
- position: Position;
- }) => void;
- keyboardEvent: React.KeyboardEvent;
- endDrag: (dragActions: FluidDragActions | null) => void;
- openPopover?: () => void;
- setDragActions: (value: React.SetStateAction) => void;
-}) => {
- let currentPosition: DOMRect | null = null;
-
- switch (keyboardEvent.key) {
- case ' ':
- if (!dragActions) {
- // start dragging, because space was pressed
- if (closePopover != null) {
- closePopover();
- }
- setDragActions(beginDrag());
- } else {
- // end dragging, because space was pressed
- endDrag(dragActions);
- setDragActions(null);
- }
- break;
- case 'Escape':
- cancelDragActions();
- break;
- case 'Tab':
- // IMPORTANT: we do NOT want to stop propagation and prevent default when Tab is pressed
- temporarilyDisableInteractiveChildTabIndexes(draggableElement);
- break;
- case 'ArrowUp':
- currentPosition = draggableElement.getBoundingClientRect();
- dragToLocation({
- dragActions,
- position: { x: currentPosition.x, y: currentPosition.y - KEYBOARD_DRAG_OFFSET },
- });
- break;
- case 'ArrowDown':
- currentPosition = draggableElement.getBoundingClientRect();
- dragToLocation({
- dragActions,
- position: { x: currentPosition.x, y: currentPosition.y + KEYBOARD_DRAG_OFFSET },
- });
- break;
- case 'ArrowLeft':
- currentPosition = draggableElement.getBoundingClientRect();
- dragToLocation({
- dragActions,
- position: { x: currentPosition.x - KEYBOARD_DRAG_OFFSET, y: currentPosition.y },
- });
- break;
- case 'ArrowRight':
- currentPosition = draggableElement.getBoundingClientRect();
- dragToLocation({
- dragActions,
- position: { x: currentPosition.x + KEYBOARD_DRAG_OFFSET, y: currentPosition.y },
- });
- break;
- case 'Enter':
- stopPropagationAndPreventDefault(keyboardEvent); // prevents the first item in the popover from getting an errant ENTER
- if (!dragActions && openPopover != null) {
- openPopover();
- }
- break;
- default:
- break;
- }
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx
index 9c6b8c485986e..f77bf0f347f79 100644
--- a/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx
@@ -21,6 +21,8 @@ import {
tooltipContentIsExplicitlyNull,
} from '.';
+jest.mock('../../lib/kibana');
+
describe('draggables', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
index b8f29996d603b..c782804b0592b 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
@@ -17,6 +17,8 @@ import { TestProviders } from '../../mock';
import { mockBrowserFields } from '../../containers/source/mock';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../../lib/kibana');
+
jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => {
return {
useRuleAsync: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx
index 204b8c088304b..1be05cc560552 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx
@@ -21,9 +21,8 @@ import { get, isEmpty } from 'lodash';
import memoizeOne from 'memoize-one';
import React from 'react';
import styled from 'styled-components';
-import { onFocusReFocusDraggable } from '../accessibility/helpers';
+import { onFocusReFocusDraggable } from '../../../../../timelines/public';
import { BrowserFields } from '../../containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { DragEffects } from '../drag_and_drop/draggable_wrapper';
import { DroppableWrapper } from '../drag_and_drop/droppable_wrapper';
import { DRAG_TYPE_FIELD, getDroppableId } from '../drag_and_drop/helpers';
@@ -38,6 +37,7 @@ import { OnUpdateColumns } from '../../../timelines/components/timeline/events';
import { getIconFromType, getExampleText } from './helpers';
import * as i18n from './translations';
import { EventFieldsData } from './types';
+import { ColumnHeaderOptions } from '../../../../common';
const HoverActionsContainer = styled(EuiPanel)`
align-items: center;
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 0c7515fe75d86..6aff259d8220e 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
@@ -20,6 +20,8 @@ import { mockAlertDetailsData } from './__mocks__';
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { TimelineTabs } from '../../../../common/types/timeline';
+jest.mock('../../../common/lib/kibana');
+
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 f0865e1b8e083..555b67da953d6 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
@@ -16,6 +16,8 @@ import { mockBrowserFields } from '../../containers/source/mock';
import { useMountAppended } from '../../utils/use_mount_appended';
import { TimelineTabs } from '../../../../common/types/timeline';
+jest.mock('../../lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx
index 93d0e6ccfbe3c..3ad7e9aef19dc 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx
@@ -11,26 +11,24 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { rgba } from 'polished';
import styled from 'styled-components';
-
import {
arrayIndexToAriaIndex,
DATA_COLINDEX_ATTRIBUTE,
DATA_ROWINDEX_ATTRIBUTE,
isTab,
onKeyDownFocusHandler,
-} from '../accessibility/helpers';
+} from '../../../../../timelines/public';
+
import { ADD_TIMELINE_BUTTON_CLASS_NAME } from '../../../timelines/components/flyout/add_timeline_button';
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { BrowserFields, getAllFieldsByName } from '../../containers/source';
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline';
import { getColumnHeaders } from '../../../timelines/components/timeline/body/column_headers/helpers';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
-
import { getColumns } from './columns';
import { EVENT_FIELDS_TABLE_CLASS_NAME, onEventDetailsTabKeyPressed, search } from './helpers';
import { useDeepEqualSelector } from '../../hooks/use_selector';
-import { TimelineTabs } from '../../../../common/types/timeline';
+import { ColumnHeaderOptions, TimelineTabs } from '../../../../common/types/timeline';
interface Props {
browserFields: BrowserFields;
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
index 1f12c2de5e24f..8392be420a2c5 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
@@ -15,15 +15,15 @@ import {
getTableSkipFocus,
handleSkipFocus,
stopPropagationAndPreventDefault,
-} from '../accessibility/helpers';
+} from '../../../../../timelines/public';
import { BrowserField, BrowserFields } from '../../containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import {
DEFAULT_DATE_COLUMN_MIN_WIDTH,
DEFAULT_COLUMN_MIN_WIDTH,
} from '../../../timelines/components/timeline/body/constants';
import * as i18n from './translations';
+import { ColumnHeaderOptions } from '../../../../common';
/**
* Defines the behavior of the search input that appears above the table of data
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx
index 7c84a325cb667..5051b39fe6093 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../common';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index 36986f5f8d353..90a4e67d76b99 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -21,9 +21,8 @@ import { mockBrowserFields, mockDocValueFields } from '../../containers/source/m
import { eventsDefaultModel } from './default_model';
import { useMountAppended } from '../../utils/use_mount_appended';
import { inputsModel } from '../../store/inputs';
-import { TimelineId } from '../../../../common/types/timeline';
+import { TimelineId, SortDirection } from '../../../../common/types/timeline';
import { KqlMode } from '../../../timelines/store/timeline/model';
-import { SortDirection } from '../../../timelines/components/timeline/body/sort';
import { AlertsTableFilterGroup } from '../../../detections/components/alerts_table/alerts_filter_group';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
@@ -31,6 +30,8 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell
import { useTimelineEvents } from '../../../timelines/containers';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
+jest.mock('../../lib/kibana');
+
jest.mock('../../hooks/use_experimental_features');
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
@@ -144,18 +145,18 @@ describe('EventsViewer', () => {
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponseWithEvents]);
});
- test('call the right reduce action to show event details', async () => {
+ test('call the right reduce action to show event details', () => {
const wrapper = mount(
);
- await act(async () => {
+ act(() => {
wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click');
});
- await waitFor(() => {
+ waitFor(() => {
expect(mockDispatch).toBeCalledTimes(2);
expect(mockDispatch.mock.calls[1][0]).toEqual({
payload: {
@@ -197,7 +198,7 @@ describe('EventsViewer', () => {
);
expect(wrapper.find(`[data-test-subj="show-field-browser"]`).first().exists()).toBe(true);
});
- // TO DO sourcerer @X
+
test('it renders the footer containing the pagination', () => {
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
index c99275ec49ab3..8326cdaaaf995 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
@@ -10,11 +10,12 @@ import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';
+import { useDispatch } from 'react-redux';
import { Direction } from '../../../../common/search_strategy';
import { BrowserFields, DocValueFields } from '../../containers/source';
import { useTimelineEvents } from '../../../timelines/containers';
import { useKibana } from '../../lib/kibana';
-import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model';
+import { KqlMode } from '../../../timelines/store/timeline/model';
import { HeaderSection } from '../header_section';
import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { Sort } from '../../../timelines/components/timeline/body/sort';
@@ -36,18 +37,21 @@ import {
Query,
} from '../../../../../../../src/plugins/data/public';
import { inputsModel } from '../../store';
-import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { ExitFullScreen } from '../exit_full_screen';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
-import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
-import { RowRenderer } from '../../../timelines/components/timeline/body/renderers/row_renderer';
+import {
+ ColumnHeaderOptions,
+ ControlColumnProps,
+ RowRenderer,
+ TimelineId,
+ TimelineTabs,
+} from '../../../../common/types/timeline';
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
-import {
- defaultControlColumn,
- ControlColumnProps,
-} from '../../../timelines/components/timeline/body/control_columns';
+import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
+import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
+import { useDeepEqualSelector } from '../../hooks/use_selector';
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
const UTILITY_BAR_HEIGHT = 19; // px
@@ -162,21 +166,19 @@ const EventsViewerComponent: React.FC = ({
utilityBar,
graphEventId,
}) => {
+ const dispatch = useDispatch();
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const [isQueryLoading, setIsQueryLoading] = useState(false);
- const { getManageTimelineById, setIsTimelineLoading } = useManageTimeline();
-
useEffect(() => {
- setIsTimelineLoading({ id, isLoading: isQueryLoading });
- }, [id, isQueryLoading, setIsTimelineLoading]);
+ dispatch(timelineActions.updateIsLoading({ id, isLoading: isQueryLoading }));
+ }, [dispatch, id, isQueryLoading]);
- const { queryFields, title, unit } = useMemo(() => getManageTimelineById(id), [
- getManageTimelineById,
- id,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const unit = useMemo(() => (n: number) => i18n.UNIT(n), []);
+ const { queryFields, title } = useDeepEqualSelector((state) => getManageTimeline(state, id));
const justTitle = useMemo(() => {title} , [title]);
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
index cd27177643b44..571e04a106cf0 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
@@ -22,6 +22,8 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell
import { useTimelineEvents } from '../../../timelines/containers';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('../../../timelines/containers', () => ({
useTimelineEvents: jest.fn(),
}));
@@ -60,7 +62,9 @@ describe('StatefulEventsViewer', () => {
await waitFor(() => {
wrapper.update();
- expect(wrapper.find('[data-test-subj="events-viewer-panel"]').first().exists()).toBe(true);
+ expect(wrapper.text()).toMatchInlineSnapshot(
+ `"Showing: 12 events1 fields sorted@timestamp1event.severityevent.categoryevent.actionhost.namesource.ipdestination.ipdestination.bytesuser.name_idmessage0 of 12 events123"`
+ );
});
});
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 b58aa2236d292..c0a75bdd3edd2 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
@@ -12,18 +12,20 @@ import styled from 'styled-components';
import { inputsModel, inputsSelectors, State } from '../../store';
import { inputsActions } from '../../store/actions';
-import { TimelineId } from '../../../../common/types/timeline';
+import { ControlColumnProps, RowRenderer, TimelineId } from '../../../../common/types/timeline';
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
import { SubsetTimelineModel, TimelineModel } from '../../../timelines/store/timeline/model';
import { Filter } from '../../../../../../../src/plugins/data/public';
-import { EventsViewer } from './events_viewer';
import { InspectButtonContainer } from '../inspect';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
+import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { useSourcererScope } from '../../containers/sourcerer';
import { DetailsPanel } from '../../../timelines/components/side_panel';
-import { RowRenderer } from '../../../timelines/components/timeline/body/renderers/row_renderer';
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
+import { useKibana } from '../../lib/kibana';
+import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
+import { EventsViewer } from './events_viewer';
const DEFAULT_EVENTS_VIEWER_HEIGHT = 652;
@@ -83,6 +85,7 @@ const StatefulEventsViewerComponent: React.FC = ({
// If truthy, the graph viewer (Resolver) is showing
graphEventId,
}) => {
+ const { timelines: timelinesUi } = useKibana().services;
const {
browserFields,
docValueFields,
@@ -90,8 +93,9 @@ const StatefulEventsViewerComponent: React.FC = ({
selectedPatterns,
loading: isLoadingIndexPattern,
} = useSourcererScope(scopeId);
- const { globalFullScreen } = useGlobalFullScreen();
-
+ const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
+ // TODO: Once we are past experimental phase this code should be removed
+ const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');
useEffect(() => {
if (createTimeline != null) {
createTimeline({
@@ -111,37 +115,73 @@ const StatefulEventsViewerComponent: React.FC = ({
}, []);
const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]);
+ const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
+ const trailingControlColumns: ControlColumnProps[] = [];
return (
<>
-
+ {tGridEnabled ? (
+ timelinesUi.getTGrid<'embedded'>({
+ type: 'embedded',
+ browserFields,
+ columns,
+ dataProviders: dataProviders!,
+ deletedEventIds,
+ docValueFields,
+ end,
+ filters: globalFilters,
+ globalFullScreen,
+ headerFilterGroup,
+ id,
+ indexNames: selectedPatterns,
+ indexPattern,
+ isLive,
+ isLoadingIndexPattern,
+ itemsPerPage,
+ itemsPerPageOptions: itemsPerPageOptions!,
+ kqlMode,
+ query,
+ onRuleChange,
+ renderCellValue,
+ rowRenderers,
+ setGlobalFullScreen,
+ start,
+ sort,
+ utilityBar,
+ graphEventId,
+ leadingControlColumns,
+ trailingControlColumns,
+ })
+ ) : (
+
+ )}
i18n.translate('xpack.securitySolution.eventsViewer.unit', {
values: { totalCount },
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx
index 7ad9de29431c9..d21adbd00cc20 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../mock';
import { Title } from './title';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../../lib/kibana');
+
describe('Title', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx
index ddbcf710aff30..a0e2ff266ad28 100644
--- a/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx
@@ -131,7 +131,7 @@ const InspectButtonComponent: React.FC = ({
color="text"
iconSide="left"
iconType="inspect"
- isDisabled={loading || isDisabled}
+ isDisabled={loading || isDisabled || false}
isLoading={loading}
onClick={handleClick}
>
@@ -145,7 +145,7 @@ const InspectButtonComponent: React.FC = ({
data-test-subj="inspect-icon-button"
iconSize="m"
iconType="inspect"
- isDisabled={loading || isDisabled}
+ isDisabled={loading || isDisabled || false}
title={i18n.INSPECT}
onClick={handleClick}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx
index 115fb65dc7011..f08edb114b9a9 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx
@@ -13,6 +13,8 @@ import { EntityDraggableComponent } from './entity_draggable';
import { TestProviders } from '../../mock/test_providers';
import { useMountAppended } from '../../utils/use_mount_appended';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx
index 6ad2bd30283d2..0d9b4001c17aa 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx
@@ -17,6 +17,8 @@ import { useMountAppended } from '../../../utils/use_mount_appended';
import { Anomalies } from '../types';
import { waitFor } from '@testing-library/dom';
+jest.mock('../../../lib/kibana');
+
const startDate: string = '2020-07-07T08:20:18.966Z';
const endDate: string = '3000-01-01T00:00:00.000Z';
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx
index 6b569a67cfebf..5eb0751404872 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx
@@ -18,6 +18,8 @@ import { Anomalies } from '../types';
import { useMountAppended } from '../../../utils/use_mount_appended';
import { waitFor } from '@testing-library/dom';
+jest.mock('../../../lib/kibana');
+
const startDate: string = '2020-07-07T08:20:18.966Z';
const endDate: string = '3000-01-01T00:00:00.000Z';
const narrowDateRange = jest.fn();
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx
index ae6ef4e680ffa..2ecda8482e340 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx
@@ -16,6 +16,8 @@ import { Columns } from '../../paginated_table';
import { TestProviders } from '../../../mock';
import { useMountAppended } from '../../../utils/use_mount_appended';
+jest.mock('../../../lib/kibana');
+
const startDate = new Date(2001).toISOString();
const endDate = new Date(3000).toISOString();
const interval = 'days';
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx
index b8a8ab88a74fd..48c2ec3ee38d8 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx
@@ -15,6 +15,8 @@ import React from 'react';
import { TestProviders } from '../../../mock';
import { useMountAppended } from '../../../utils/use_mount_appended';
+jest.mock('../../../../common/lib/kibana');
+
const startDate = new Date(2001).toISOString();
const endDate = new Date(3000).toISOString();
describe('get_anomalies_network_table_columns', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx
index 8c2b97a4b8b38..c122138f9547a 100644
--- a/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx
@@ -18,6 +18,9 @@ import {
import { TestProviders } from '../../mock';
import { getEmptyValue } from '../empty_value';
import { useMountAppended } from '../../utils/use_mount_appended';
+
+jest.mock('../../lib/kibana');
+
describe('Table Helpers', () => {
const items = ['item1', 'item2', 'item3'];
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts
index 70e095c88576f..04ceafde7ef74 100644
--- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts
+++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts
@@ -8,10 +8,10 @@
import type React from 'react';
import uuid from 'uuid';
import { isError } from 'lodash/fp';
+import { isAppError } from '@kbn/securitysolution-t-grid';
import { AppToast, ActionToaster } from './';
import { isToasterError } from './errors';
-import { isAppError } from '../../utils/api';
/**
* Displays an error toast for the provided title and message
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 005602738f376..4f6834e84d83a 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
@@ -18,17 +18,11 @@ import {
createSecuritySolutionStorageMock,
mockIndexPattern,
} from '../../mock';
-import { FilterManager } from '../../../../../../../src/plugins/data/public';
import { createStore, State } from '../../store';
import { Props } from './top_n';
import { StatefulTopN } from '.';
-import {
- ManageGlobalTimeline,
- getTimelineDefaults,
-} from '../../../timelines/components/manage_timeline';
import { TimelineId } from '../../../../common/types/timeline';
-import { coreMock } from '../../../../../../../src/core/public/mocks';
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
@@ -45,8 +39,6 @@ jest.mock('../link_to');
jest.mock('../../lib/kibana');
jest.mock('../../../timelines/store/timeline/actions');
-const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
-
const field = 'process.name';
const value = 'nice';
@@ -175,9 +167,7 @@ describe('StatefulTopN', () => {
beforeEach(() => {
wrapper = mount(
-
-
-
+
);
});
@@ -244,26 +234,16 @@ describe('StatefulTopN', () => {
});
describe('rendering in a timeline context', () => {
- let filterManager: FilterManager;
let wrapper: ReactWrapper;
beforeEach(() => {
- filterManager = new FilterManager(mockUiSettingsForFilterManager);
- const manageTimelineForTesting = {
- [TimelineId.active]: {
- ...getTimelineDefaults(TimelineId.active),
- filterManager,
- },
- };
testProps = {
...testProps,
timelineId: TimelineId.active,
};
wrapper = mount(
-
-
-
+
);
});
@@ -320,25 +300,13 @@ describe('StatefulTopN', () => {
});
describe('rendering in a NON-active timeline context', () => {
test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, async () => {
- const filterManager = new FilterManager(mockUiSettingsForFilterManager);
-
- const manageTimelineForTesting = {
- [TimelineId.active]: {
- ...getTimelineDefaults(TimelineId.active),
- filterManager,
- documentType: 'alerts',
- },
- };
-
testProps = {
...testProps,
timelineId: TimelineId.detectionsPage,
};
const wrapper = mount(
-
-
-
+
);
await waitFor(() => {
diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx
index a8868436d9689..c867862e690bd 100644
--- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx
@@ -6,13 +6,13 @@
*/
import { EuiPopover } from '@elastic/eui';
+import {
+ HOVER_ACTIONS_ALWAYS_SHOW_CLASS_NAME,
+ IS_DRAGGING_CLASS_NAME,
+} from '@kbn/securitysolution-t-grid';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
-import { IS_DRAGGING_CLASS_NAME } from '../drag_and_drop/helpers';
-
-export const HOVER_ACTIONS_ALWAYS_SHOW_CLASS_NAME = 'hover-actions-always-show';
-
/**
* To avoid expensive changes to the DOM, delay showing the popover menu
*/
diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
index 3e690e50b04b1..4f558412576b4 100644
--- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
@@ -83,7 +83,7 @@ export const useTimelineLastEventTime = ({
TimelineEventsLastEventTimeRequestOptions,
TimelineEventsLastEventTimeStrategyResponse
>(request, {
- strategy: 'securitySolutionTimelineSearchStrategy',
+ strategy: 'timelineSearchStrategy',
abortSignal: abortCtrl.current.signal,
})
.subscribe({
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
index 1c17f95bb6ba0..3bc92dafd351f 100644
--- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
@@ -151,7 +151,7 @@ export const useFetchIndex = (
{ indices: iNames, onlyCheckIfIndicesExist },
{
abortSignal: abortCtrl.current.signal,
- strategy: 'securitySolutionIndexFields',
+ strategy: 'indexFields',
}
)
.subscribe({
@@ -235,7 +235,7 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => {
{ indices: indicesName, onlyCheckIfIndicesExist: false },
{
abortSignal: abortCtrl.current.signal,
- strategy: 'securitySolutionIndexFields',
+ strategy: 'indexFields',
}
)
.subscribe({
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts
index da6b41080c1c7..6c5caa25a1f96 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts
@@ -7,9 +7,10 @@
import { renderHook } from '@testing-library/react-hooks';
import { IEsError } from 'src/plugins/data/public';
+import { KibanaError, SecurityAppError } from '@kbn/securitysolution-t-grid';
import { useToasts } from '../lib/kibana';
-import { KibanaError, SecurityAppError } from '../utils/api';
+
import {
appErrorToErrorStack,
convertErrorToEnumerable,
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts
index 61b20e137f870..0c2721e6ad416 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts
@@ -7,11 +7,17 @@
import { useCallback, useRef } from 'react';
import { isString } from 'lodash/fp';
+import {
+ AppError,
+ isAppError,
+ isKibanaError,
+ isSecurityAppError,
+} from '@kbn/securitysolution-t-grid';
+
import { IEsError, isEsError } from '../../../../../../src/plugins/data/public';
import { ErrorToastOptions, ToastsStart, Toast } from '../../../../../../src/core/public';
import { useToasts } from '../lib/kibana';
-import { AppError, isAppError, isKibanaError, isSecurityAppError } from '../utils/api';
export type UseAppToasts = Pick & {
api: ToastsStart;
diff --git a/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx b/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx
index 1baa57166de3f..2f5afc8a44489 100644
--- a/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx
+++ b/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx
@@ -6,9 +6,10 @@
*/
import { EuiToolTip } from '@elastic/eui';
+
import React from 'react';
-import { TooltipWithKeyboardShortcut } from '../../components/accessibility/tooltip_with_keyboard_shortcut';
+import { TooltipWithKeyboardShortcut } from '../../components/accessibility';
import * as i18n from '../../components/drag_and_drop/translations';
import { Clipboard } from './clipboard';
diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
index eb0ae1ae1dee9..09c3d2537e272 100644
--- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
@@ -6,6 +6,10 @@
*/
import { notificationServiceMock } from '../../../../../../../../src/core/public/mocks';
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { createTGridMocks } from '../../../../../../timelines/public/mock';
+
import {
createKibanaContextProviderMock,
createUseUiSettingMock,
@@ -30,14 +34,24 @@ export const useKibana = jest.fn().mockReturnValue({
})),
})),
},
+ query: {
+ ...mockStartServicesMock.data.query,
+ filterManager: {
+ addFilters: jest.fn(),
+ getFilters: jest.fn(),
+ getUpdates$: jest.fn().mockReturnValue({ subscribe: jest.fn() }),
+ setAppFilters: jest.fn(),
+ },
+ },
},
+ timelines: createTGridMocks(),
},
});
export const useUiSetting = jest.fn(createUseUiSettingMock());
export const useUiSetting$ = jest.fn(createUseUiSetting$Mock());
export const useHttp = jest.fn().mockReturnValue(createStartServicesMock().http);
export const useTimeZone = jest.fn();
-export const useDateFormat = jest.fn();
+export const useDateFormat = jest.fn().mockReturnValue('MMM D, YYYY @ HH:mm:ss.SSS');
export const useBasePath = jest.fn(() => '/test/base/path');
export const useToasts = jest
.fn()
diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
index 557c04e4e8a47..316f8b6214d1e 100644
--- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
@@ -43,6 +43,7 @@ export const mockGlobalState: State = {
trustedAppsByPolicyEnabled: false,
metricsEntitiesEnabled: false,
ruleRegistryEnabled: false,
+ tGridEnabled: false,
},
},
hosts: {
diff --git a/x-pack/plugins/security_solution/public/common/mock/header.ts b/x-pack/plugins/security_solution/public/common/mock/header.ts
index ae7d3c9e576a8..029ddb00d1832 100644
--- a/x-pack/plugins/security_solution/public/common/mock/header.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/header.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ColumnHeaderOptions } from '../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../common';
import { defaultColumnHeaderType } from '../../timelines/components/timeline/body/column_headers/default_headers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_control_columns.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_control_columns.tsx
index 7604732f90203..7dae3e671d271 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_control_columns.tsx
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_control_columns.tsx
@@ -15,7 +15,7 @@ import {
EuiPopoverTitle,
EuiSpacer,
} from '@elastic/eui';
-import { ControlColumnProps } from '../../timelines/components/timeline/body/control_columns';
+import { ControlColumnProps } from '../../../common/types/timeline';
const SelectionHeaderCell = () => {
return (
diff --git a/x-pack/plugins/security_solution/public/common/mock/utils.ts b/x-pack/plugins/security_solution/public/common/mock/utils.ts
index 30951b81611db..e0f8e651a5821 100644
--- a/x-pack/plugins/security_solution/public/common/mock/utils.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/utils.ts
@@ -5,12 +5,20 @@
* 2.0.
*/
+import { AnyAction, Reducer } from 'redux';
+import reduceReducers from 'reduce-reducers';
+
+import { tGridReducer } from '../../../../timelines/public';
+
import { hostsReducer } from '../../hosts/store';
import { networkReducer } from '../../network/store';
import { timelineReducer } from '../../timelines/store/timeline/reducer';
import { managementReducer } from '../../management/store/reducer';
import { ManagementPluginReducer } from '../../management';
import { SubPluginsInitReducer } from '../store';
+import { mockGlobalState } from './global_state';
+import { TimelineState } from '../../timelines/store/timeline/types';
+import { defaultHeaders } from '../../timelines/components/timeline/body/column_headers/default_headers';
interface Global extends NodeJS.Global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -19,10 +27,32 @@ interface Global extends NodeJS.Global {
export const globalNode: Global = global;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const combineTimelineReducer = reduceReducers(
+ {
+ ...mockGlobalState.timeline,
+ timelineById: {
+ ...mockGlobalState.timeline.timelineById,
+ test: {
+ ...mockGlobalState.timeline.timelineById.test,
+ defaultColumns: defaultHeaders,
+ loadingText: 'events',
+ footerText: 'events',
+ documentType: '',
+ selectAll: false,
+ queryFields: [],
+ unit: (n: number) => n,
+ },
+ },
+ },
+ tGridReducer,
+ timelineReducer
+) as Reducer;
+
export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = {
hosts: hostsReducer,
network: networkReducer,
- timeline: timelineReducer,
+ timeline: combineTimelineReducer,
/**
* These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture,
* they are cast to mutable versions here.
diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts
index e784f6cebae17..5791a4940cbed 100644
--- a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts
+++ b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts
@@ -60,6 +60,7 @@ export interface GlobalGenericQuery {
isInspected: boolean;
loading: boolean;
selectedInspectIndex: number;
+ invalidKqlQuery?: Error;
}
export interface GlobalGraphqlQuery extends GlobalGenericQuery {
diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts
index fbf4caad9793d..21e833abe1f9b 100644
--- a/x-pack/plugins/security_solution/public/common/store/types.ts
+++ b/x-pack/plugins/security_solution/public/common/store/types.ts
@@ -37,18 +37,6 @@ export type StoreState = HostsPluginState &
*/
export type State = CombinedState;
-export type KueryFilterQueryKind = 'kuery' | 'lucene' | 'eql';
-
-export interface KueryFilterQuery {
- kind: KueryFilterQueryKind;
- expression: string;
-}
-
-export interface SerializedFilterQuery {
- kuery: KueryFilterQuery | null;
- serializedQuery: string;
-}
-
/**
* like redux's `MiddlewareAPI` but `getState` returns an `Immutable` version of
* state and `dispatch` accepts `Immutable` versions of actions.
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
index e5cefca66d0fd..601e0509009ce 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
@@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n';
import type { Filter } from '../../../../../../../src/plugins/data/common/es_query/filters';
import {
+ KueryFilterQueryKind,
TimelineId,
TimelineResult,
TimelineStatus,
@@ -44,7 +45,6 @@ import {
replaceTemplateFieldFromMatchFilters,
replaceTemplateFieldFromDataProviders,
} from './helpers';
-import { KueryFilterQueryKind } from '../../../common/store';
import {
DataProvider,
QueryOperator,
@@ -399,7 +399,7 @@ export const sendAlertToTimelineAction = async ({
factoryQueryType: TimelineEventsQueries.details,
},
{
- strategy: 'securitySolutionTimelineSearchStrategy',
+ strategy: 'timelineSearchStrategy',
}
)
.toPromise(),
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx
index 4ca2980dc74e5..a3d3bf4834376 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx
@@ -11,6 +11,7 @@ import { shallow, mount } from 'enzyme';
import { AlertsUtilityBar, AlertsUtilityBarProps } from './index';
import { TestProviders } from '../../../../common/mock/test_providers';
+jest.useFakeTimers();
jest.mock('../../../../common/lib/kibana');
describe('AlertsUtilityBar', () => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
index 02a815bc59f3b..9a142f6cba247 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
@@ -6,11 +6,11 @@
*/
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
-import { RowRendererId } from '../../../../common/types/timeline';
+import { ColumnHeaderOptions, RowRendererId } from '../../../../common/types/timeline';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { Filter } from '../../../../../../../src/plugins/data/common/es_query';
-import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model';
+import { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { columns } from '../../configurations/security_solution_detections/columns';
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index f20754fc446d6..a27368cc61c3a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -8,11 +8,11 @@
import { EuiPanel, EuiLoadingContent } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { connect, ConnectedProps } from 'react-redux';
+import { connect, ConnectedProps, useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { Filter, esQuery } from '../../../../../../../src/plugins/data/public';
-import { TimelineIdLiteral } from '../../../../common/types/timeline';
+import { RowRendererId, TimelineIdLiteral } from '../../../../common/types/timeline';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
import { HeaderSection } from '../../../common/components/header_section';
@@ -23,8 +23,6 @@ import { inputsSelectors, State, inputsModel } from '../../../common/store';
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
import { TimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
-import { useManageTimeline } from '../../../timelines/components/manage_timeline';
-
import { updateAlertStatusAction } from './actions';
import {
requiredFieldsForActions,
@@ -95,6 +93,7 @@ export const AlertsTableComponent: React.FC = ({
timelineId,
to,
}) => {
+ const dispatch = useDispatch();
const [showClearSelectionAction, setShowClearSelectionAction] = useState(false);
const [filterGroup, setFilterGroup] = useState(FILTER_OPEN);
const {
@@ -106,7 +105,6 @@ export const AlertsTableComponent: React.FC = ({
const kibana = useKibana();
const [, dispatchToaster] = useStateToaster();
const { addWarning } = useAppToasts();
- const { initializeTimeline, setSelectAll } = useManageTimeline();
// TODO: Once we are past experimental phase this code should be removed
const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled');
@@ -195,14 +193,16 @@ export const AlertsTableComponent: React.FC = ({
// Catches state change isSelectAllChecked->false upon user selection change to reset utility bar
useEffect(() => {
if (isSelectAllChecked) {
- setSelectAll({
- id: timelineId,
- selectAll: false,
- });
+ dispatch(
+ timelineActions.setTGridSelectAll({
+ id: timelineId,
+ selectAll: false,
+ })
+ );
} else {
setShowClearSelectionAction(false);
}
- }, [isSelectAllChecked, setSelectAll, timelineId]);
+ }, [dispatch, isSelectAllChecked, timelineId]);
// Callback for when open/closed filter changes
const onFilterGroupChangedCallback = useCallback(
@@ -218,23 +218,27 @@ export const AlertsTableComponent: React.FC = ({
// Callback for clearing entire selection from utility bar
const clearSelectionCallback = useCallback(() => {
clearSelected!({ id: timelineId });
- setSelectAll({
- id: timelineId,
- selectAll: false,
- });
+ dispatch(
+ timelineActions.setTGridSelectAll({
+ id: timelineId,
+ selectAll: false,
+ })
+ );
setShowClearSelectionAction(false);
- }, [clearSelected, setSelectAll, setShowClearSelectionAction, timelineId]);
+ }, [clearSelected, dispatch, timelineId]);
// Callback for selecting all events on all pages from utility bar
// Dispatches to stateful_body's selectAll via TimelineTypeContext props
// as scope of response data required to actually set selectedEvents
const selectAllOnAllPagesCallback = useCallback(() => {
- setSelectAll({
- id: timelineId,
- selectAll: true,
- });
+ dispatch(
+ timelineActions.setTGridSelectAll({
+ id: timelineId,
+ selectAll: true,
+ })
+ );
setShowClearSelectionAction(true);
- }, [setSelectAll, setShowClearSelectionAction, timelineId]);
+ }, [dispatch, timelineId]);
const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback(
async (
@@ -330,22 +334,22 @@ export const AlertsTableComponent: React.FC = ({
: alertsDefaultModel;
useEffect(() => {
- initializeTimeline({
- defaultModel: {
- ...defaultTimelineModel,
- columns,
- },
- documentType: i18n.ALERTS_DOCUMENT_TYPE,
- filterManager,
- footerText: i18n.TOTAL_COUNT_OF_ALERTS,
- id: timelineId,
- loadingText: i18n.LOADING_ALERTS,
- selectAll: false,
- queryFields: requiredFieldsForActions,
- title: '',
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ dispatch(
+ timelineActions.initializeTGridSettings({
+ defaultColumns: columns,
+ documentType: i18n.ALERTS_DOCUMENT_TYPE,
+ excludedRowRendererIds: defaultTimelineModel.excludedRowRendererIds as RowRendererId[],
+ filterManager,
+ footerText: i18n.TOTAL_COUNT_OF_ALERTS,
+ id: timelineId,
+ loadingText: i18n.LOADING_ALERTS,
+ selectAll: false,
+ queryFields: requiredFieldsForActions,
+ title: '',
+ showCheckboxes: true,
+ })
+ );
+ }, [dispatch, defaultTimelineModel, filterManager, timelineId]);
const headerFilterGroup = useMemo(
() => ,
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts
index 8cbb532501a2c..70d2237a535eb 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts
+++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts
@@ -6,10 +6,9 @@
*/
import { EuiDataGridColumn } from '@elastic/eui';
-
+import { ColumnHeaderOptions } from '../../../../../common';
import { defaultColumnHeaderType } from '../../../../timelines/components/timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../../timelines/components/timeline/body/constants';
-import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model';
import * as i18n from '../../../components/alerts_table/translations';
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx
index 9c2114a4ef085..7db75d3a73d90 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx
@@ -15,10 +15,12 @@ import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../com
import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline';
import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
-import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../common';
import { RenderCellValue } from '.';
+jest.mock('../../../../common/lib/kibana/');
+
describe('RenderCellValue', () => {
const columnId = '@timestamp';
const eventId = '_id-123';
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts
index 96d2d870b1270..3365ce5432940 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts
+++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts
@@ -6,10 +6,9 @@
*/
import { EuiDataGridColumn } from '@elastic/eui';
-
+import { ColumnHeaderOptions } from '../../../../../common';
import { defaultColumnHeaderType } from '../../../../timelines/components/timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../../timelines/components/timeline/body/constants';
-import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model';
import * as i18n from '../../../components/alerts_table/translations';
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx
index aa4eb543a3d9b..a8f295df2540d 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx
@@ -15,9 +15,11 @@ import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../com
import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline';
import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
-import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model';
import { RenderCellValue } from '.';
+import { ColumnHeaderOptions } from '../../../../../common';
+
+jest.mock('../../../../common/lib/kibana/');
describe('RenderCellValue', () => {
const columnId = '@timestamp';
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts
index 23a0740294e84..7f46c839ffe62 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts
+++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts
@@ -6,13 +6,13 @@
*/
import { EuiDataGridColumn } from '@elastic/eui';
+import { ColumnHeaderOptions } from '../../../../common';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
DEFAULT_DATE_COLUMN_MIN_WIDTH,
} from '../../../timelines/components/timeline/body/constants';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import * as i18n from '../../components/alerts_table/translations';
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
index 18350c102c049..965ee913a1daa 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
@@ -9,16 +9,18 @@ import { mount } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
+import { ColumnHeaderOptions } from '../../../../common';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { DragDropContextWrapper } from '../../../common/components/drag_and_drop/drag_drop_context_wrapper';
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock';
import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { RenderCellValue } from '.';
+jest.mock('../../../common/lib/kibana');
+
describe('RenderCellValue', () => {
const columnId = '@timestamp';
const eventId = '_id-123';
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx
index 84eaf8e3aa93c..6f8d938dd987e 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx
@@ -6,13 +6,13 @@
*/
import { useEffect, useState } from 'react';
+import { isSecurityAppError } from '@kbn/securitysolution-t-grid';
import { DEFAULT_ALERTS_INDEX } from '../../../../../common/constants';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { createSignalIndex, getSignalIndex } from './api';
import * as i18n from './translations';
-import { isSecurityAppError } from '../../../../common/utils/api';
import { useAlertsPrivileges } from './use_alerts_privileges';
type Func = () => Promise;
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx
index 8e231f0d1fdbb..d55d171708963 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx
@@ -6,10 +6,9 @@
*/
import { useEffect, useState, useCallback } from 'react';
-
+import { isSecurityAppError } from '@kbn/securitysolution-t-grid';
import { useReadListIndex, useCreateListIndex } from '@kbn/securitysolution-list-hooks';
import { useHttp, useKibana } from '../../../../common/lib/kibana';
-import { isSecurityAppError } from '../../../../common/utils/api';
import * as i18n from './translations';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { useListsPrivileges } from './use_lists_privileges';
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx
index f848b71cf7bd3..4f524886935cd 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx
@@ -6,8 +6,8 @@
*/
import { useEffect, useRef, useState } from 'react';
+import { isNotFoundError } from '@kbn/securitysolution-t-grid';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
-import { isNotFoundError } from '../../../../common/utils/api';
import { RuleStatusRowItemType } from '../../../pages/detection_engine/rules/all/columns';
import { getRuleStatusById, getRulesStatusByIds } from './api';
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
index 4a39e486b6fd5..abd5a2781c8a7 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
@@ -6,11 +6,11 @@
*/
import { renderHook, act } from '@testing-library/react-hooks';
+import { SecurityAppError } from '@kbn/securitysolution-t-grid';
import { useRuleWithFallback } from './use_rule_with_fallback';
import * as api from './api';
import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
-import { SecurityAppError } from '../../../../common/utils/api';
jest.mock('./api');
jest.mock('../alerts/api');
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx
index 11c30547848c3..da56275280f65 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx
@@ -6,9 +6,9 @@
*/
import { useCallback, useEffect, useMemo } from 'react';
+import { isNotFoundError } from '@kbn/securitysolution-t-grid';
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
-import { isNotFoundError } from '../../../../common/utils/api';
import { useQueryAlerts } from '../alerts/use_query';
import { fetchRuleById } from './api';
import { transformInput } from './transforms';
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index 8ae7e4fb2852b..1c31dfd3b8907 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -11,13 +11,13 @@ import { noop } from 'lodash/fp';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
+import { isTab } from '../../../../../timelines/public';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
import { SecurityPageName } from '../../../app/types';
import { TimelineId } from '../../../../common/types/timeline';
import { useGlobalTime } from '../../../common/containers/use_global_time';
-import { isTab } from '../../../common/components/accessibility/helpers';
import { UpdateDateRange } from '../../../common/components/charts/common';
import { FiltersGlobal } from '../../../common/components/filters_global';
import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine';
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx
index dd3549ea20d36..8cc3113a5706a 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx
@@ -42,6 +42,9 @@ describe('ExceptionListsTable', () => {
addError: jest.fn(),
},
},
+ timelines: {
+ getLastUpdated: () => null,
+ },
},
});
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx
index 7f734b10fd020..35404f4486bc3 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx
@@ -26,7 +26,6 @@ import { Loader } from '../../../../../../common/components/loader';
import { Panel } from '../../../../../../common/components/panel';
import * as i18n from './translations';
import { AllRulesUtilityBar } from '../utility_bar';
-import { LastUpdatedAt } from '../../../../../../common/components/last_updated';
import { AllExceptionListsColumns, getAllExceptionListsColumns } from './columns';
import { useAllExceptionLists } from './use_all_exception_lists';
import { ReferenceErrorModal } from '../../../../../components/value_lists_management_modal/reference_error_modal';
@@ -62,7 +61,7 @@ const exceptionReferenceModalInitialState: ReferenceModalState = {
export const ExceptionListsTable = React.memo(
({ formatUrl, history, hasPermissions, loading }) => {
const {
- services: { http, notifications },
+ services: { http, notifications, timelines },
} = useKibana();
const { exportExceptionList, deleteExceptionList } = useApi(http);
@@ -344,7 +343,7 @@ export const ExceptionListsTable = React.memo(
}
+ subtitle={timelines.getLastUpdated({ showUpdating: loading, updatedAt: lastUpdated })}
>
{!initLoading && }
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
index 8fd82a495e52f..2ec34aaece60b 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
@@ -47,7 +47,6 @@ import { hasMlAdminPermissions } from '../../../../../../common/machine_learning
import { hasMlLicense } from '../../../../../../common/machine_learning/has_ml_license';
import { isBoolean } from '../../../../../common/utils/privileges';
import { AllRulesUtilityBar } from './utility_bar';
-import { LastUpdatedAt } from '../../../../../common/components/last_updated';
import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/constants';
import { AllRulesTabs } from '.';
import { useValueChanged } from '../../../../../common/hooks/use_value_changed';
@@ -104,6 +103,7 @@ export const RulesTables = React.memo(
application: {
capabilities: { actions },
},
+ timelines,
},
} = useKibana();
@@ -473,12 +473,10 @@ export const RulesTables = React.memo(
split
growLeftSplit={false}
title={i18n.ALL_RULES}
- subtitle={
-
- }
+ subtitle={timelines.getLastUpdated({
+ showUpdating: loading || isLoadingRules || isLoadingRulesStatuses,
+ updatedAt: lastUpdated,
+ })}
>
{shouldShowRulesTable && (
({
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 751a2bf5a2055..2cd4ed1f57f84 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
@@ -20,6 +20,8 @@ import { mockData } from './mock';
import { HostsType } from '../../store/model';
import * as i18n from './translations';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 2333d5e9b127c..b51e20b801f40 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
@@ -19,6 +19,8 @@ import { type } from './utils';
import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { getHostDetailsPageFilters } from './helpers';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('../../../common/components/url_state/normalize_time_range.ts');
jest.mock('../../../common/containers/source', () => ({
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 57cded85d67cc..ce0385b532fd5 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -11,6 +11,7 @@ import { noop } from 'lodash/fp';
import React, { useCallback, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
+import { isTab } from '../../../../timelines/public';
import { SecurityPageName } from '../../app/types';
import { UpdateDateRange } from '../../common/components/charts/common';
@@ -42,7 +43,6 @@ import * as i18n from './translations';
import { filterHostData } from './navigation';
import { hostsModel } from '../store';
import { HostsTableType } from '../store/model';
-import { isTab } from '../../common/components/accessibility/helpers';
import {
onTimelineTabKeyPressed,
resetKeyboardFocus,
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
index f88709e6e95ac..973dbc41925da 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
@@ -10,6 +10,7 @@ import { useDispatch } from 'react-redux';
import { TimelineId } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
+import { timelineActions } from '../../../timelines/store/timeline';
import { HostsComponentsQueryProps } from './types';
import { eventsDefaultModel } from '../../../common/components/events_viewer/default_model';
import {
@@ -20,7 +21,6 @@ import { MatrixHistogram } from '../../../common/components/matrix_histogram';
import { useGlobalFullScreen } from '../../../common/containers/use_full_screen';
import * as i18n from '../translations';
import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution';
-import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
@@ -64,14 +64,15 @@ const EventsQueryTabBodyComponent: React.FC = ({
startDate,
}) => {
const dispatch = useDispatch();
- const { initializeTimeline } = useManageTimeline();
const { globalFullScreen } = useGlobalFullScreen();
useEffect(() => {
- initializeTimeline({
- id: TimelineId.hostsPageEvents,
- defaultModel: eventsDefaultModel,
- });
- }, [dispatch, initializeTimeline]);
+ dispatch(
+ timelineActions.initializeTGridSettings({
+ id: TimelineId.hostsPageEvents,
+ defaultColumns: eventsDefaultModel.columns,
+ })
+ );
+ }, [dispatch]);
useEffect(() => {
return () => {
diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts
index 55262fe039b4e..3d2412b326b54 100644
--- a/x-pack/plugins/security_solution/public/index.ts
+++ b/x-pack/plugins/security_solution/public/index.ts
@@ -8,6 +8,7 @@
import { PluginInitializerContext } from '../../../../src/core/public';
import { Plugin } from './plugin';
import { PluginSetup } from './types';
+export type { TimelineModel } from './timelines/store/timeline/model';
export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context);
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 a3fd32008062c..63971ae508d5c 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
@@ -14,6 +14,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { Ip } from '.';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx
index 7ec18c078c73d..a811f5c92c37a 100644
--- a/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx
@@ -25,6 +25,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { NetworkDnsTable } from '.';
import { mockData } from './mock';
+jest.mock('../../../common/lib/kibana');
+
describe('NetworkTopNFlow Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
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 f7f75d9f0a365..f05372c76b36f 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
@@ -25,6 +25,7 @@ import { networkModel } from '../../store';
import { NetworkHttpTable } from '.';
import { mockData } from './mock';
+jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/components/link_to');
describe('NetworkHttp Table Component', () => {
diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx
index 1501f56882290..a0727fad65f18 100644
--- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx
@@ -27,6 +27,8 @@ import { networkModel } from '../../store';
import { NetworkTopCountriesTable } from '.';
import { mockData } from './mock';
+jest.mock('../../../common/lib/kibana');
+
describe('NetworkTopCountries 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 cd8c8c6543299..e2b9447b58806 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
@@ -25,6 +25,7 @@ import { NetworkTopNFlowTable } from '.';
import { mockData } from './mock';
import { FlowTargetSourceDest } from '../../../../common/search_strategy';
+jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/components/link_to');
describe('NetworkTopNFlow Table Component', () => {
diff --git a/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx
index ef1039bfc92e3..dd7ad20d2384a 100644
--- a/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx
@@ -15,6 +15,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { Port } from '.';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 01065ad5bf15f..b59eb25cbfe25 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
@@ -49,6 +49,8 @@ import {
NETWORK_TRANSPORT_FIELD_NAME,
} from './field_names';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 f767e793c8f21..91f7ea3d7ac7a 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
@@ -38,6 +38,8 @@ import {
SOURCE_GEO_REGION_NAME_FIELD_NAME,
} from './geo_fields';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('../../../common/components/link_to');
describe('SourceDestinationIp', () => {
diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx
index 4b6c31f5b6176..8f2c7a098a045 100644
--- a/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx
@@ -24,6 +24,8 @@ import { networkModel } from '../../store';
import { TlsTable } from '.';
import { mockTlsData } from './mock';
+jest.mock('../../../common/lib/kibana');
+
describe('Tls Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
diff --git a/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx
index 4b613e79a1d1a..69027ad9bd9f8 100644
--- a/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx
@@ -26,6 +26,8 @@ import { UsersTable } from '.';
import { mockUsersData } from './mock';
import { FlowTarget } from '../../../../common/search_strategy';
+jest.mock('../../../common/lib/kibana');
+
describe('Users Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
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 2bcc72d932a9b..dbfb250095ee2 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx
@@ -12,6 +12,7 @@ import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
+import { isTab } from '../../../../timelines/public';
import { esQuery } from '../../../../../../src/plugins/data/public';
import { SecurityPageName } from '../../app/types';
import { UpdateDateRange } from '../../common/components/charts/common';
@@ -46,7 +47,6 @@ import {
showGlobalFilters,
} from '../../timelines/components/timeline/helpers';
import { timelineSelectors } from '../../timelines/store/timeline';
-import { isTab } from '../../common/components/accessibility/helpers';
import { TimelineId } from '../../../common/types/timeline';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { useSourcererScope } from '../../common/containers/sourcerer';
diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx
index b43d5af029ec4..45898427ee60b 100644
--- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx
@@ -15,6 +15,8 @@ import { TestProviders } from '../../../../common/mock';
import { EndpointOverview } from './index';
import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy/security_solution/hosts';
+jest.mock('../../../../common/lib/kibana');
+
describe('EndpointOverview Component', () => {
test('it renders with endpoint data', () => {
const endpointData = {
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index 781ed8ffdaa54..5a44faa58414a 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -6,8 +6,10 @@
*/
import { i18n } from '@kbn/i18n';
+import reduceReducers from 'reduce-reducers';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { pluck } from 'rxjs/operators';
+import { AnyAction, Reducer } from 'redux';
import {
PluginSetup,
PluginStart,
@@ -72,6 +74,7 @@ import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/vi
import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension';
import { parseExperimentalConfigValue } from '../common/experimental_features';
+import type { TimelineState } from '../../timelines/public';
export class Plugin implements IPlugin {
private kibanaVersion: string;
@@ -471,7 +474,7 @@ export class Plugin implements IPlugin(
{ indices: defaultIndicesName, onlyCheckIfIndicesExist: true },
{
- strategy: 'securitySolutionIndexFields',
+ strategy: 'indexFields',
}
)
.toPromise(),
@@ -500,7 +503,6 @@ export class Plugin implements IPlugin;
+
this._store = createStore(
createInitialState(
{
@@ -531,13 +540,17 @@ export class Plugin implements IPlugin {
const mount = useMountAppended();
test('renders the expected label', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx
index 4c90d3738a198..ea8317346cd99 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx
@@ -14,6 +14,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { Duration } from '.';
+jest.mock('../../../common/lib/kibana');
+
describe('Duration', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx
index 5becf7ea8bc6b..e2194156ecf4d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx
@@ -29,6 +29,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { AutonomousSystem, FlowTarget } from '../../../../common/search_strategy';
import { HostEcs } from '../../../../common/ecs/host';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/categories_pane.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/categories_pane.tsx
index 77a8d0082bf23..da2ff248d9a5d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/categories_pane.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/categories_pane.tsx
@@ -14,7 +14,7 @@ import {
DATA_COLINDEX_ATTRIBUTE,
DATA_ROWINDEX_ATTRIBUTE,
onKeyDownFocusHandler,
-} from '../../../common/components/accessibility/helpers';
+} from '../../../../../timelines/public';
import { BrowserFields } from '../../../common/containers/source';
import { getCategoryColumns } from './category_columns';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx
index c3c55206f8d53..c95463dea5b27 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx
@@ -17,6 +17,9 @@ import { TestProviders } from '../../../common/mock';
import { useMountAppended } from '../../../common/utils/use_mount_appended';
import * as i18n from './translations';
+
+jest.mock('../../../common/lib/kibana');
+
describe('Category', () => {
const timelineId = 'test';
const selectedCategoryId = 'client';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.tsx
index 636ebf022cffb..deafda95ceab2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.tsx
@@ -9,13 +9,13 @@ import { EuiInMemoryTable } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { useCallback, useMemo, useRef } from 'react';
import styled from 'styled-components';
-
import {
arrayIndexToAriaIndex,
DATA_COLINDEX_ATTRIBUTE,
DATA_ROWINDEX_ATTRIBUTE,
onKeyDownFocusHandler,
-} from '../../../common/components/accessibility/helpers';
+} from '../../../../../timelines/public';
+
import { BrowserFields } from '../../../common/containers/source';
import { OnUpdateColumns } from '../timeline/events';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_columns.tsx
index 15164cd151574..528791328fdb9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_columns.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_columns.tsx
@@ -18,6 +18,7 @@ import {
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
+import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { BrowserFields } from '../../../common/containers/source';
import { getColumnsWithTimestamp } from '../../../common/components/event_details/helpers';
import { CountBadge } from '../../../common/components/page';
@@ -29,7 +30,7 @@ import {
VIEW_ALL_BUTTON_CLASS_NAME,
} from './helpers';
import * as i18n from './translations';
-import { useManageTimeline } from '../manage_timeline';
+import { timelineSelectors } from '../../store/timeline';
const CategoryName = styled.span<{ bold: boolean }>`
.euiText {
@@ -67,11 +68,10 @@ interface ViewAllButtonProps {
export const ViewAllButton = React.memo(
({ categoryId, browserFields, onUpdateColumns, timelineId }) => {
- const { getManageTimelineById } = useManageTimeline();
- const { isLoading } = useMemo(() => getManageTimelineById(timelineId) ?? { isLoading: false }, [
- getManageTimelineById,
- timelineId,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { isLoading } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId ?? '')
+ );
const handleClick = useCallback(() => {
onUpdateColumns(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_title.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_title.test.tsx
index 70cc535cb59a9..6af4b5c5c312e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_title.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category_title.test.tsx
@@ -9,6 +9,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { mockBrowserFields } from '../../../common/containers/source/mock';
+import { TestProviders } from '../../../common/mock';
import { CategoryTitle } from './category_title';
import { getFieldCount } from './helpers';
@@ -19,12 +20,14 @@ describe('CategoryTitle', () => {
test('it renders the category id as the value of the title', () => {
const categoryId = 'client';
const wrapper = mount(
-
+
+
+
);
expect(wrapper.find('[data-test-subj="selected-category-title"]').first().text()).toEqual(
@@ -35,12 +38,14 @@ describe('CategoryTitle', () => {
test('when `categoryId` specifies a valid category in `filteredBrowserFields`, a count of the field is displayed in the badge', () => {
const validCategoryId = 'client';
const wrapper = mount(
-
+
+
+
);
expect(wrapper.find(`[data-test-subj="selected-category-count-badge"]`).first().text()).toEqual(
@@ -51,12 +56,14 @@ describe('CategoryTitle', () => {
test('when `categoryId` specifies an INVALID category in `filteredBrowserFields`, a count of zero is displayed in the badge', () => {
const invalidCategoryId = 'this.is.not.happening';
const wrapper = mount(
-
+
+
+
);
expect(wrapper.find(`[data-test-subj="selected-category-count-badge"]`).first().text()).toEqual(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.tsx
index c4f76c639c7c1..0496b9d7c8886 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.tsx
@@ -19,13 +19,8 @@ import { noop } from 'lodash/fp';
import styled from 'styled-components';
import { useDispatch } from 'react-redux';
-import {
- isEscape,
- isTab,
- stopPropagationAndPreventDefault,
-} from '../../../common/components/accessibility/helpers';
+import { isEscape, isTab, stopPropagationAndPreventDefault } from '../../../../../timelines/public';
import { BrowserFields } from '../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { CategoriesPane } from './categories_pane';
import { FieldsPane } from './fields_pane';
import { Header } from './header';
@@ -42,6 +37,7 @@ import { FieldBrowserProps, OnHideFieldBrowser } from './types';
import { timelineActions } from '../../store/timeline';
import * as i18n from './translations';
+import { ColumnHeaderOptions } from '../../../../common';
const FieldsBrowserContainer = styled.div<{ width: number }>`
background-color: ${({ theme }) => theme.eui.euiColorLightestShade};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx
index 07911541bb2fe..e40807dc85dc7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx
@@ -12,7 +12,6 @@ import { waitFor } from '@testing-library/react';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { TestProviders } from '../../../common/mock';
import '../../../common/mock/match_media';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../timeline/body/constants';
@@ -20,6 +19,9 @@ import { Category } from './category';
import { getFieldColumns, getFieldItems } from './field_items';
import { FIELDS_PANE_WIDTH } from './helpers';
import { useMountAppended } from '../../../common/utils/use_mount_appended';
+import { ColumnHeaderOptions } from '../../../../common';
+
+jest.mock('../../../common/lib/kibana');
const selectedCategoryId = 'base';
const selectedCategoryFields = mockBrowserFields[selectedCategoryId].fields;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx
index a2db284e51790..89a91ee6da305 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx
@@ -18,14 +18,12 @@ import React, { useCallback, useRef, useState } from 'react';
import { Draggable } from 'react-beautiful-dnd';
import styled from 'styled-components';
+import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import { BrowserField, BrowserFields } from '../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
-import { useDraggableKeyboardWrapper } from '../../../common/components/drag_and_drop/draggable_keyboard_wrapper_hook';
import { DragEffects } from '../../../common/components/drag_and_drop/draggable_wrapper';
import { DroppableWrapper } from '../../../common/components/drag_and_drop/droppable_wrapper';
import {
DRAG_TYPE_FIELD,
- DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME,
getDraggableFieldId,
getDroppableId,
} from '../../../common/components/drag_and_drop/helpers';
@@ -43,6 +41,8 @@ import { TruncatableText } from '../../../common/components/truncatable_text';
import { FieldName } from './field_name';
import * as i18n from './translations';
import { getAlertColumnHeader } from './helpers';
+import { ColumnHeaderOptions } from '../../../../common';
+import { useKibana } from '../../../common/lib/kibana';
const TypeIcon = styled(EuiIcon)`
margin: 0 4px;
@@ -92,6 +92,7 @@ const DraggableFieldsBrowserFieldComponent = ({
const keyboardHandlerRef = useRef(null);
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState(false);
+ const { timelines } = useKibana().services;
const handleClosePopOverTrigger = useCallback(() => {
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
@@ -115,7 +116,7 @@ const DraggableFieldsBrowserFieldComponent = ({
setHoverActionsOwnFocus(true);
}, [setHoverActionsOwnFocus]);
- const { onBlur, onKeyDown } = useDraggableKeyboardWrapper({
+ const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
closePopover: handleClosePopOverTrigger,
draggableId: getDraggableFieldId({
contextId: `field-browser-field-items-field-draggable-${timelineId}-${categoryId}-${fieldName}`,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx
index 493f2e44263e3..5014a198e8bd5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx
@@ -15,6 +15,8 @@ import { getColumnsWithTimestamp } from '../../../common/components/event_detail
import { FieldName } from './field_name';
+jest.mock('../../../common/lib/kibana');
+
const categoryId = 'base';
const timestampFieldId = '@timestamp';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
index 09bd18ef62fb1..2e76e43227506 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
@@ -9,13 +9,13 @@ import { EuiHighlight, EuiText } from '@elastic/eui';
import React, { useCallback, useState, useMemo, useRef } from 'react';
import styled from 'styled-components';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { OnUpdateColumns } from '../timeline/events';
import { WithHoverActions } from '../../../common/components/with_hover_actions';
import {
DraggableWrapperHoverContent,
useGetTimelineId,
} from '../../../common/components/drag_and_drop/draggable_wrapper_hover_content';
+import { ColumnHeaderOptions } from '../../../../common';
/**
* The name of a (draggable) field
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx
index 3f1b0300ad70d..6d17f148aa1dc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx
@@ -15,6 +15,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { FIELDS_PANE_WIDTH } from './helpers';
import { FieldsPane } from './fields_pane';
+jest.mock('../../../common/lib/kibana');
+
const timelineId = 'test';
describe('FieldsPane', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.tsx
index 15df232a1a454..dfb4edad17414 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.tsx
@@ -11,7 +11,6 @@ import styled from 'styled-components';
import { useDispatch } from 'react-redux';
import { BrowserFields } from '../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { timelineActions } from '../../../timelines/store/timeline';
import { OnUpdateColumns } from '../timeline/events';
import { Category } from './category';
@@ -20,6 +19,7 @@ import { getFieldItems } from './field_items';
import { FIELDS_PANE_WIDTH, TABLE_HEIGHT } from './helpers';
import * as i18n from './translations';
+import { ColumnHeaderOptions } from '../../../../common';
const NoFieldsPanel = styled.div`
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.test.tsx
index aa53b1922f3a3..89b361e86422e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.test.tsx
@@ -9,7 +9,6 @@ import { mount } from 'enzyme';
import React from 'react';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { TestProviders } from '../../../common/mock';
-import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
import { Header } from './header';
const timelineId = 'test';
@@ -72,7 +71,7 @@ describe('Header', () => {
wrapper.find('[data-test-subj="reset-fields"]').first().simulate('click');
- expect(onUpdateColumns).toBeCalledWith(defaultHeaders);
+ expect(onUpdateColumns).toBeCalled();
});
test('it invokes onOutsideClick when the user clicks the Reset Fields button', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx
index 120a82a4046e3..b52c6cd672ac7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx
@@ -13,10 +13,12 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
-import React, { useCallback } from 'react';
+import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { BrowserFields } from '../../../common/containers/source';
+import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
+import { timelineSelectors } from '../../store/timeline';
import { OnUpdateColumns } from '../timeline/events';
import {
@@ -27,7 +29,6 @@ import {
} from './helpers';
import * as i18n from './translations';
-import { useManageTimeline } from '../manage_timeline';
const CountsFlexGroup = styled(EuiFlexGroup)`
margin-top: 5px;
@@ -101,13 +102,13 @@ const TitleRow = React.memo<{
onOutsideClick: () => void;
onUpdateColumns: OnUpdateColumns;
}>(({ id, onOutsideClick, onUpdateColumns }) => {
- const { getManageTimelineById } = useManageTimeline();
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { defaultColumns } = useDeepEqualSelector((state) => getManageTimeline(state, id));
const handleResetColumns = useCallback(() => {
- const timeline = getManageTimelineById(id);
- onUpdateColumns(timeline.defaultModel.columns);
+ onUpdateColumns(defaultColumns);
onOutsideClick();
- }, [id, onUpdateColumns, onOutsideClick, getManageTimelineById]);
+ }, [onUpdateColumns, onOutsideClick, defaultColumns]);
return (
{
const timelineId = 'test';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/types.ts b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/types.ts
index 4d912f73c7ef2..ea71a8860ab01 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/types.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/types.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { ColumnHeaderOptions } from '../../../../common';
import { BrowserFields } from '../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
export type OnFieldSelected = (fieldId: string) => void;
export type OnHideFieldBrowser = () => void;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx
index 7b43fb9c7194c..feaf7b7513bc1 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx
@@ -10,7 +10,7 @@ import { rgba } from 'polished';
import React from 'react';
import styled from 'styled-components';
-import { IS_DRAGGING_CLASS_NAME } from '../../../../common/components/drag_and_drop/helpers';
+import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import { DataProvider } from '../../timeline/data_providers/data_provider';
import { flattenIntoAndGroups } from '../../timeline/data_providers/helpers';
import { DataProviders } from '../../timeline/data_providers';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx
index 802dd74c1892b..31f2fec942490 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx
@@ -14,6 +14,8 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended';
import { Ja3Fingerprint } from '.';
+jest.mock('../../../common/lib/kibana');
+
describe('Ja3Fingerprint', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx
deleted file mode 100644
index ed299c3a4ef1a..0000000000000
--- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { renderHook, act } from '@testing-library/react-hooks';
-import { getTimelineDefaults, useTimelineManager, UseTimelineManager } from './';
-import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
-import { coreMock } from '../../../../../../../src/core/public/mocks';
-
-const isStringifiedComparisonEqual = (a: {}, b: {}): boolean =>
- JSON.stringify(a) === JSON.stringify(b);
-
-describe('useTimelineManager', () => {
- const setupMock = coreMock.createSetup();
- const testId = 'coolness';
- const timelineDefaults = getTimelineDefaults(testId);
- const mockFilterManager = new FilterManager(setupMock.uiSettings);
-
- beforeEach(() => {
- jest.clearAllMocks();
- jest.restoreAllMocks();
- });
-
- it('initializes an undefined timeline', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useTimelineManager()
- );
- await waitForNextUpdate();
- const uninitializedTimeline = result.current.getManageTimelineById(testId);
- expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy();
- });
- });
- // TO DO sourcerer
- // it('getIndexToAddById', async () => {
- // await act(async () => {
- // const { result, waitForNextUpdate } = renderHook(() =>
- // useTimelineManager()
- // );
- // await waitForNextUpdate();
- // const data = result.current.getIndexToAddById(testId);
- // expect(data).toEqual(timelineDefaults.indexToAdd);
- // });
- // });
- //
- // it('setIndexToAdd', async () => {
- // await act(async () => {
- // const indexToAddArgs = { id: testId, indexToAdd: ['example'] };
- // const { result, waitForNextUpdate } = renderHook(() =>
- // useTimelineManager()
- // );
- // await waitForNextUpdate();
- // result.current.initializeTimeline({
- // id: testId,
- // });
- // result.current.setIndexToAdd(indexToAddArgs);
- // const data = result.current.getIndexToAddById(testId);
- // expect(data).toEqual(indexToAddArgs.indexToAdd);
- // });
- // });
-
- it('setIsTimelineLoading', async () => {
- await act(async () => {
- const isLoadingArgs = { id: testId, isLoading: true };
- const { result, waitForNextUpdate } = renderHook(() =>
- useTimelineManager()
- );
- await waitForNextUpdate();
- result.current.initializeTimeline({
- id: testId,
- });
- let timeline = result.current.getManageTimelineById(testId);
- expect(timeline.isLoading).toBeFalsy();
- result.current.setIsTimelineLoading(isLoadingArgs);
- timeline = result.current.getManageTimelineById(testId);
- expect(timeline.isLoading).toBeTruthy();
- });
- });
-
- it('getTimelineFilterManager undefined on uninitialized', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useTimelineManager()
- );
- await waitForNextUpdate();
- const data = result.current.getTimelineFilterManager(testId);
- expect(data).toEqual(undefined);
- });
- });
-
- it('getTimelineFilterManager defined at initialize', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useTimelineManager()
- );
- await waitForNextUpdate();
- result.current.initializeTimeline({
- id: testId,
- filterManager: mockFilterManager,
- });
- const data = result.current.getTimelineFilterManager(testId);
- expect(data).toEqual(mockFilterManager);
- });
- });
-
- it('isManagedTimeline returns false when unset and then true when set', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useTimelineManager()
- );
- await waitForNextUpdate();
- let data = result.current.isManagedTimeline(testId);
- expect(data).toBeFalsy();
- result.current.initializeTimeline({
- id: testId,
- filterManager: mockFilterManager,
- });
- data = result.current.isManagedTimeline(testId);
- expect(data).toBeTruthy();
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx
deleted file mode 100644
index 1f215ee8f2141..0000000000000
--- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { createContext, useCallback, useContext, useReducer } from 'react';
-import { noop } from 'lodash/fp';
-
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
-import { SubsetTimelineModel } from '../../store/timeline/model';
-import * as i18n from '../../../common/components/events_viewer/translations';
-import * as i18nF from '../timeline/footer/translations';
-import { timelineDefaults as timelineDefaultModel } from '../../store/timeline/defaults';
-
-interface ManageTimelineInit {
- documentType?: string;
- defaultModel?: SubsetTimelineModel;
- filterManager?: FilterManager;
- footerText?: string;
- id: string;
- loadingText?: string;
- selectAll?: boolean;
- queryFields?: string[];
- title?: string;
- unit?: (totalCount: number) => string;
-}
-
-interface ManageTimeline {
- documentType: string;
- defaultModel: SubsetTimelineModel;
- filterManager?: FilterManager;
- footerText: string;
- id: string;
- isLoading: boolean;
- loadingText: string;
- queryFields: string[];
- selectAll: boolean;
- title: string;
- unit: (totalCount: number) => string;
-}
-
-interface ManageTimelineById {
- [id: string]: ManageTimeline;
-}
-const initManageTimeline: ManageTimelineById = {};
-type ActionManageTimeline =
- | {
- type: 'INITIALIZE_TIMELINE';
- id: string;
- payload: ManageTimelineInit;
- }
- | {
- type: 'SET_IS_LOADING';
- id: string;
- payload: boolean;
- }
- | {
- type: 'SET_SELECT_ALL';
- id: string;
- payload: boolean;
- };
-
-export const getTimelineDefaults = (id: string) => ({
- defaultModel: timelineDefaultModel,
- loadingText: i18n.LOADING_EVENTS,
- footerText: i18nF.TOTAL_COUNT_OF_EVENTS,
- documentType: i18nF.TOTAL_COUNT_OF_EVENTS,
- selectAll: false,
- id,
- isLoading: false,
- queryFields: [],
- title: i18n.EVENTS,
- unit: (n: number) => i18n.UNIT(n),
-});
-const reducerManageTimeline = (
- state: ManageTimelineById,
- action: ActionManageTimeline
-): ManageTimelineById => {
- switch (action.type) {
- case 'INITIALIZE_TIMELINE':
- return {
- ...state,
- [action.id]: {
- ...getTimelineDefaults(action.id),
- ...state[action.id],
- ...action.payload,
- },
- } as ManageTimelineById;
- case 'SET_SELECT_ALL':
- return {
- ...state,
- [action.id]: {
- ...state[action.id],
- selectAll: action.payload,
- },
- } as ManageTimelineById;
-
- case 'SET_IS_LOADING':
- return {
- ...state,
- [action.id]: {
- ...state[action.id],
- isLoading: action.payload,
- },
- } as ManageTimelineById;
- default:
- return state;
- }
-};
-
-export interface UseTimelineManager {
- getManageTimelineById: (id: string) => ManageTimeline;
- getTimelineFilterManager: (id: string) => FilterManager | undefined;
- initializeTimeline: (newTimeline: ManageTimelineInit) => void;
- isManagedTimeline: (id: string) => boolean;
- setIsTimelineLoading: (isLoadingArgs: { id: string; isLoading: boolean }) => void;
- setSelectAll: (selectAllArgs: { id: string; selectAll: boolean }) => void;
-}
-
-export const useTimelineManager = (
- manageTimelineForTesting?: ManageTimelineById
-): UseTimelineManager => {
- const [state, dispatch] = useReducer<
- (state: ManageTimelineById, action: ActionManageTimeline) => ManageTimelineById
- >(reducerManageTimeline, manageTimelineForTesting ?? initManageTimeline);
-
- const initializeTimeline = useCallback((newTimeline: ManageTimelineInit) => {
- dispatch({
- type: 'INITIALIZE_TIMELINE',
- id: newTimeline.id,
- payload: newTimeline,
- });
- }, []);
-
- const setIsTimelineLoading = useCallback(
- ({ id, isLoading }: { id: string; isLoading: boolean }) => {
- dispatch({
- type: 'SET_IS_LOADING',
- id,
- payload: isLoading,
- });
- },
- []
- );
-
- const setSelectAll = useCallback(({ id, selectAll }: { id: string; selectAll: boolean }) => {
- dispatch({
- type: 'SET_SELECT_ALL',
- id,
- payload: selectAll,
- });
- }, []);
-
- const getTimelineFilterManager = useCallback(
- (id: string): FilterManager | undefined => state[id]?.filterManager,
- [state]
- );
- const getManageTimelineById = useCallback(
- (id: string): ManageTimeline => {
- if (state[id] != null) {
- return state[id];
- }
- initializeTimeline({ id });
- return getTimelineDefaults(id);
- },
- [initializeTimeline, state]
- );
- const isManagedTimeline = useCallback((id: string): boolean => state[id] != null, [state]);
-
- return {
- getManageTimelineById,
- getTimelineFilterManager,
- initializeTimeline,
- isManagedTimeline,
- setIsTimelineLoading,
- setSelectAll,
- };
-};
-
-const init = {
- getManageTimelineById: (id: string) => getTimelineDefaults(id),
- getTimelineFilterManager: () => undefined,
- initializeTimeline: () => noop,
- isManagedTimeline: () => false,
- setIsTimelineLoading: () => noop,
- setSelectAll: () => noop,
-};
-
-const ManageTimelineContext = createContext(init);
-
-export const useManageTimeline = () => useContext(ManageTimelineContext);
-
-interface ManageGlobalTimelineProps {
- children: React.ReactNode;
- manageTimelineForTesting?: ManageTimelineById;
-}
-
-export const ManageGlobalTimeline = ({
- children,
- manageTimelineForTesting,
-}: ManageGlobalTimelineProps) => {
- const timelineManager = useTimelineManager(manageTimelineForTesting);
-
- return (
-
- {children}
-
- );
-};
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 e2c8b8854504a..c73e372b4a71c 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
@@ -62,6 +62,8 @@ import {
} from '../../../network/components/source_destination/field_names';
import { useMountAppended } from '../../../common/utils/use_mount_appended';
+jest.mock('../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx
index 0544b00a79227..00d2a7b35483e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx
@@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiPanel, EuiScreenReaderOnly } from '@elastic/eui';
import React, { useState, useCallback } from 'react';
import styled from 'styled-components';
-import { getNotesContainerClassName } from '../../../../common/components/accessibility/helpers';
+import { getNotesContainerClassName } from '../../../../../../timelines/public';
import { AddNote } from '../add_note';
import { AssociateNote } from '../helpers';
import { NotePreviews, NotePreviewsContainer } from '../../open_timeline/note_previews';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
index c06c3f076e097..c0fea1f210a8a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
@@ -36,7 +36,6 @@ import {
formatTimelineResultToModel,
} from './helpers';
import { OpenTimelineResult, DispatchUpdateTimeline } from './types';
-import { KueryFilterQueryKind } from '../../../common/store';
import { Note } from '../../../common/lib/note';
import moment from 'moment';
import sinon from 'sinon';
@@ -45,6 +44,7 @@ import {
TimelineType,
TimelineStatus,
TimelineTabs,
+ KueryFilterQueryKind,
} from '../../../../common/types/timeline';
import {
mockTimeline as mockSelectedTimeline,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
index e45a1a117769b..03ac0b3d14342 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
@@ -13,6 +13,7 @@ import { Dispatch } from 'redux';
import deepMerge from 'deepmerge';
import {
+ ColumnHeaderOptions,
DataProviderType,
TimelineId,
TimelineStatus,
@@ -37,7 +38,7 @@ import {
addTimeline as dispatchAddTimeline,
addNote as dispatchAddGlobalTimelineNote,
} from '../../../timelines/store/timeline/actions';
-import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model';
+import { TimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx
index 9887563c0fef6..2daebdf37e77f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx
@@ -16,41 +16,28 @@ import {
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
-import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
+import {
+ HeaderActionProps,
+ SortDirection,
+ TimelineId,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
import { EXIT_FULL_SCREEN } from '../../../../../common/components/exit_full_screen/translations';
import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../../../common/constants';
import {
useGlobalFullScreen,
useTimelineFullScreen,
} from '../../../../../common/containers/use_full_screen';
-import { BrowserFields } from '../../../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
-import { OnSelectAll } from '../../events';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
import { StatefulFieldsBrowser } from '../../../fields_browser';
import { StatefulRowRenderersBrowser } from '../../../row_renderers_browser';
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers';
import { EventsTh, EventsThContent } from '../../styles';
-import { Sort, SortDirection } from '../sort';
import { EventsSelect } from '../column_headers/events_select';
import * as i18n from '../column_headers/translations';
import { timelineActions } from '../../../../store/timeline';
import { isFullScreen } from '../column_headers';
-export interface HeaderActionProps {
- width: number;
- browserFields: BrowserFields;
- columnHeaders: ColumnHeaderOptions[];
- isEventViewer?: boolean;
- isSelectAllChecked: boolean;
- onSelectAll: OnSelectAll;
- showEventsSelect: boolean;
- showSelectAllCheckbox: boolean;
- sort: Sort[];
- tabType: TimelineTabs;
- timelineId: string;
-}
-
const SortingColumnsContainer = styled.div`
button {
color: ${({ theme }) => theme.eui.euiColorPrimary};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
index a186b324cc03a..82d593e80bc44 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
@@ -41,8 +41,6 @@ describe('Actions', () => {
eventId="abc"
loadingEventIds={[]}
onEventDetailsPanelOpened={jest.fn()}
- onPinEvent={jest.fn()}
- onUnPinEvent={jest.fn()}
onRowSelected={jest.fn()}
showNotes={false}
isEventPinned={false}
@@ -74,8 +72,6 @@ describe('Actions', () => {
toggleShowNotes={jest.fn()}
timelineId={'test'}
refetch={jest.fn()}
- onPinEvent={jest.fn()}
- onUnPinEvent={jest.fn()}
columnId={''}
index={2}
eventId="abc"
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
index 2053b9a0da942..0a3a1cd88accc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
@@ -6,7 +6,9 @@
*/
import React, { useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
+import { noop } from 'lodash/fp';
import {
eventHasNotes,
getEventType,
@@ -22,45 +24,9 @@ import * as i18n from '../translations';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
import { AddToCaseAction } from '../../../../../cases/components/timeline_actions/add_to_case_action';
-import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
-import { timelineSelectors } from '../../../../store/timeline';
+import { TimelineId, ActionProps, OnPinEvent } from '../../../../../../common/types/timeline';
+import { timelineActions, timelineSelectors } from '../../../../store/timeline';
import { timelineDefaults } from '../../../../store/timeline/defaults';
-import { Ecs } from '../../../../../../common/ecs';
-import { inputsModel } from '../../../../../common/store';
-import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events';
-import { RowCellRender } from '../control_columns';
-
-interface Props {
- ariaRowindex: number;
- action?: RowCellRender;
- width?: number;
- columnId: string;
- columnValues: string;
- checked: boolean;
- onRowSelected: OnRowSelected;
- eventId: string;
- loadingEventIds: Readonly;
- onEventDetailsPanelOpened: () => void;
- showCheckboxes: boolean;
- data: TimelineNonEcsData[];
- ecsData: Ecs;
- index: number;
- eventIdToNoteIds: Readonly>;
- isEventPinned: boolean;
- isEventViewer?: boolean;
- onPinEvent: OnPinEvent;
- onUnPinEvent: OnUnPinEvent;
- refetch: inputsModel.Refetch;
- rowIndex: number;
- onRuleChange?: () => void;
- showNotes: boolean;
- tabType?: TimelineTabs;
- timelineId: string;
- toggleShowNotes: () => void;
-}
-
-export type ActionProps = Props;
const ActionsComponent: React.FC = ({
ariaRowindex,
@@ -75,9 +41,7 @@ const ActionsComponent: React.FC = ({
isEventViewer = false,
loadingEventIds,
onEventDetailsPanelOpened,
- onPinEvent,
onRowSelected,
- onUnPinEvent,
refetch,
onRuleChange,
showCheckboxes,
@@ -85,9 +49,20 @@ const ActionsComponent: React.FC = ({
timelineId,
toggleShowNotes,
}) => {
+ const dispatch = useDispatch();
const emptyNotes: string[] = [];
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
+ const onPinEvent: OnPinEvent = useCallback(
+ (evtId) => dispatch(timelineActions.pinEvent({ id: timelineId, eventId: evtId })),
+ [dispatch, timelineId]
+ );
+
+ const onUnPinEvent: OnPinEvent = useCallback(
+ (evtId) => dispatch(timelineActions.unPinEvent({ id: timelineId, eventId: evtId })),
+ [dispatch, timelineId]
+ );
+
const handleSelectEvent = useCallback(
(event: React.ChangeEvent) =>
onRowSelected({
@@ -99,7 +74,7 @@ const ActionsComponent: React.FC = ({
const handlePinClicked = useCallback(
() =>
getPinOnClick({
- allowUnpinning: !eventHasNotes(eventIdToNoteIds[eventId]),
+ allowUnpinning: eventIdToNoteIds ? !eventHasNotes(eventIdToNoteIds[eventId]) : true,
eventId,
onPinEvent,
onUnPinEvent,
@@ -164,12 +139,12 @@ const ActionsComponent: React.FC = ({
/>
)}
- {!isEventViewer && (
+ {!isEventViewer && toggleShowNotes && (
<>
@@ -177,7 +152,7 @@ const ActionsComponent: React.FC = ({
ariaLabel={i18n.PIN_EVENT_FOR_ROW({ ariaRowindex, columnValues, isEventPinned })}
key="pin-event"
onPinClicked={handlePinClicked}
- noteIds={eventIdToNoteIds[eventId] || emptyNotes}
+ noteIds={eventIdToNoteIds ? eventIdToNoteIds[eventId] || emptyNotes : emptyNotes}
eventIsPinned={isEventPinned}
timelineType={timelineType}
/>
@@ -200,7 +175,7 @@ const ActionsComponent: React.FC = ({
ecsRowData={ecsData}
timelineId={timelineId}
disabled={eventType !== 'signal' && !isEventContextMenuEnabled}
- refetch={refetch}
+ refetch={refetch ?? noop}
onRuleChange={onRuleChange}
/>
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx
index f9eda55c237ae..8795255dfcfd4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx
@@ -8,7 +8,7 @@
import { EuiButtonIcon } from '@elastic/eui';
import React, { useCallback } from 'react';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../../../common';
import { OnColumnRemoved } from '../../../events';
import { EventsHeadingExtra, EventsLoading } from '../../../styles';
import { Sort } from '../../sort';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
index 3ab4d564391f3..74593e40ddf4c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
@@ -12,16 +12,12 @@ import { Resizable, ResizeCallback } from 're-resizable';
import deepEqual from 'fast-deep-equal';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
+import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '@kbn/securitysolution-t-grid';
-import { useDraggableKeyboardWrapper } from '../../../../../common/components/drag_and_drop/draggable_keyboard_wrapper_hook';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../constants';
-import {
- DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME,
- getDraggableFieldId,
-} from '../../../../../common/components/drag_and_drop/helpers';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
+import { getDraggableFieldId } from '../../../../../common/components/drag_and_drop/helpers';
+import { ColumnHeaderOptions, TimelineTabs } from '../../../../../../common/types/timeline';
import { Direction } from '../../../../../../common/search_strategy';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import { OnFilterChange } from '../../events';
import { ARIA_COLUMN_INDEX_OFFSET } from '../../helpers';
import { EventsTh, EventsThContent, EventsHeadingHandle } from '../../styles';
@@ -31,6 +27,7 @@ import { Header } from './header';
import { timelineActions } from '../../../../store/timeline';
import * as i18n from './translations';
+import { useKibana } from '../../../../../common/lib/kibana';
const ContextMenu = styled(EuiContextMenu)`
width: 115px;
@@ -75,6 +72,7 @@ const ColumnHeaderComponent: React.FC = ({
const restoreFocus = useCallback(() => keyboardHandlerRef.current?.focus(), []);
const dispatch = useDispatch();
+ const { timelines } = useKibana().services;
const resizableSize = useMemo(
() => ({
width: header.initialWidth ?? DEFAULT_COLUMN_MIN_WIDTH,
@@ -247,7 +245,7 @@ const ColumnHeaderComponent: React.FC = ({
setHoverActionsOwnFocus(true);
}, []);
- const { onBlur, onKeyDown } = useDraggableKeyboardWrapper({
+ const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
closePopover: handleClosePopOverTrigger,
draggableId,
fieldName: header.id,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts
index fea65d0499a13..7eb98b7475952 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { ColumnHeaderOptions, ColumnHeaderType } from '../../../../store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../../common';
+import { ColumnHeaderType } from '../../../../store/timeline/model';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../constants';
export const defaultColumnHeaderType: ColumnHeaderType = 'not-filtered';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx
index bdf4cc42fa794..828b8d8701188 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx
@@ -8,9 +8,9 @@
import { noop } from 'lodash/fp';
import React from 'react';
+import { ColumnHeaderOptions } from '../../../../../../../common';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../constants';
import { OnFilterChange } from '../../../events';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
import { TextFilter } from '../text_filter';
interface Props {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
index 484cb78417c2f..ffab38b64bef8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
@@ -8,8 +8,8 @@
import { EuiToolTip } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React from 'react';
+import { ColumnHeaderOptions } from '../../../../../../../common/types/timeline';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
import { TruncatableText } from '../../../../../../common/components/truncatable_text';
import { EventsHeading, EventsHeadingTitleButton, EventsHeadingTitleSpan } from '../../../styles';
import { Sort } from '../../sort';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/helpers.ts
index b52fa292413df..257b88944c14e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/helpers.ts
@@ -6,9 +6,8 @@
*/
import { Direction } from '../../../../../../../common/search_strategy';
-import { assertUnreachable } from '../../../../../../../common/utility_types';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
-import { Sort, SortDirection } from '../../sort';
+import { ColumnHeaderOptions, SortDirection } from '../../../../../../../common/types/timeline';
+import { Sort } from '../../sort';
interface GetNewSortDirectionOnClickParams {
clickedHeader: ColumnHeaderOptions;
@@ -35,7 +34,7 @@ export const getNextSortDirection = (currentSort: Sort): Direction => {
case 'none':
return Direction.desc;
default:
- return assertUnreachable(currentSort.sortDirection, 'Unhandled sort direction');
+ return Direction.desc;
}
};
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 f2496484c25ea..4fa72fa5da424 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
@@ -18,6 +18,7 @@ import { defaultHeaders } from '../default_headers';
import { HeaderComponent } from '.';
import { getNewSortDirectionOnClick, getNextSortDirection, getSortDirection } from './helpers';
import { Direction } from '../../../../../../../common/search_strategy';
+import { useDeepEqualSelector } from '../../../../../../common/hooks/use_selector';
const mockDispatch = jest.fn();
jest.mock('react-redux', () => {
@@ -30,6 +31,11 @@ jest.mock('react-redux', () => {
};
});
+jest.mock('../../../../../../common/hooks/use_selector', () => ({
+ useShallowEqualSelector: jest.fn(),
+ useDeepEqualSelector: jest.fn(),
+}));
+
const filteredColumnHeader: ColumnHeaderType = 'text-filter';
describe('Header', () => {
@@ -41,7 +47,11 @@ describe('Header', () => {
sortDirection: Direction.desc,
},
];
- const timelineId = 'fakeId';
+ const timelineId = 'test';
+
+ beforeEach(() => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: false });
+ });
test('renders correctly against snapshot', () => {
const wrapper = shallow(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
index ece28faedb951..60a241a340d99 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
@@ -9,16 +9,18 @@ import { noop } from 'lodash/fp';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
-import { useShallowEqualSelector } from '../../../../../../common/hooks/use_selector';
-import { timelineActions } from '../../../../../store/timeline';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../../../common';
+import {
+ useDeepEqualSelector,
+ useShallowEqualSelector,
+} from '../../../../../../common/hooks/use_selector';
+import { timelineActions, timelineSelectors } from '../../../../../store/timeline';
import { OnFilterChange } from '../../../events';
import { Sort } from '../../sort';
import { Actions } from '../actions';
import { Filter } from '../filter';
import { getNewSortDirectionOnClick } from './helpers';
import { HeaderContent } from './header_content';
-import { useManageTimeline } from '../../../../manage_timeline';
import { isEqlOnSelector } from './selectors';
interface Props {
@@ -80,12 +82,10 @@ export const HeaderComponent: React.FC = ({
[dispatch, timelineId]
);
- const { getManageTimelineById } = useManageTimeline();
-
- const isLoading = useMemo(() => getManageTimelineById(timelineId).isLoading, [
- getManageTimelineById,
- timelineId,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { isLoading } = useDeepEqualSelector(
+ (state) => getManageTimeline(state, timelineId) || { isLoading: false }
+ );
const showSortingCapability = !isEqlOn && !(header.subType && header.subType.nested);
return (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx
index 5b5a8b10591d4..b33e47dd27b96 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx
@@ -9,9 +9,8 @@ import { mount, shallow } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../../../common';
import { defaultHeaders } from '../../../../../../common/mock';
-
import { HeaderToolTipContent } from '.';
describe('HeaderToolTipContent', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx
index f4e7b6459bd14..0ae8dbb537fb8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx
@@ -10,7 +10,7 @@ import { isEmpty } from 'lodash/fp';
import React from 'react';
import styled from 'styled-components';
-import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
+import { ColumnHeaderOptions } from '../../../../../../../common';
import { getIconFromType } from '../../../../../../common/components/event_details/helpers';
import * as i18n from '../translations';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
index d19c5689ab049..c49d088d6241d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
@@ -6,9 +6,9 @@
*/
import { get } from 'lodash/fp';
+import { ColumnHeaderOptions } from '../../../../../../common';
import { BrowserFields } from '../../../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import {
DEFAULT_COLUMN_MIN_WIDTH,
DEFAULT_DATE_COLUMN_MIN_WIDTH,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx
index 41f9db3f1c25b..378f7fce250fe 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx
@@ -24,6 +24,8 @@ import { Direction } from '../../../../../../common/search_strategy';
import { defaultControlColumn } from '../control_columns';
import { testTrailingControlColumns } from '../../../../../common/mock/mock_timeline_control_columns';
+jest.mock('../../../../../common/lib/kibana');
+
const mockDispatch = jest.fn();
jest.mock('react-redux', () => {
const original = jest.requireActual('react-redux');
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
index 3b0b935bfcff4..25aefd513f806 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
@@ -11,12 +11,17 @@ import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd';
import { DragEffects } from '../../../../../common/components/drag_and_drop/draggable_wrapper';
import { DraggableFieldBadge } from '../../../../../common/components/draggables/field_badge';
import { BrowserFields } from '../../../../../common/containers/source';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import {
DRAG_TYPE_FIELD,
droppableTimelineColumnsPrefix,
} from '../../../../../common/components/drag_and_drop/helpers';
-import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
+import {
+ ColumnHeaderOptions,
+ ControlColumnProps,
+ HeaderActionProps,
+ TimelineId,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
import { OnSelectAll } from '../../events';
import {
EventsTh,
@@ -27,8 +32,6 @@ import {
} from '../../styles';
import { Sort } from '../sort';
import { ColumnHeader } from './column_header';
-import { ControlColumnProps } from '../control_columns';
-import { HeaderActionProps } from '../actions/header_actions';
interface Props {
actionsColumnWidth: number;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/control_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/control_columns/index.tsx
index 8ef69697af1d0..e4f4c26417351 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/control_columns/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/control_columns/index.tsx
@@ -5,48 +5,9 @@
* 2.0.
*/
-import { ComponentType, JSXElementConstructor } from 'react';
-import { EuiDataGridControlColumn, EuiDataGridCellValueElementProps } from '@elastic/eui';
-import { OnRowSelected } from '../../events';
-import { ActionProps, Actions } from '../actions';
-import { HeaderActions, HeaderActionProps } from '../actions/header_actions';
-
-export type GenericActionRowCellRenderProps = Pick<
- EuiDataGridCellValueElementProps,
- 'rowIndex' | 'columnId'
->;
-
-export type HeaderCellRender = ComponentType | ComponentType;
-export type RowCellRender =
- | JSXElementConstructor
- | ((props: GenericActionRowCellRenderProps) => JSX.Element)
- | JSXElementConstructor
- | ((props: ActionProps) => JSX.Element);
-
-interface AdditionalControlColumnProps {
- ariaRowindex: number;
- actionsColumnWidth: number;
- columnValues: string;
- checked: boolean;
- onRowSelected: OnRowSelected;
- eventId: string;
- id: string;
- columnId: string;
- loadingEventIds: Readonly;
- onEventDetailsPanelOpened: () => void;
- showCheckboxes: boolean;
- // Override these type definitions to support either a generic custom component or the one used in security_solution today.
- headerCellRender: HeaderCellRender;
- rowCellRender: RowCellRender;
- // If not provided, calculated dynamically
- width?: number;
-}
-
-export type ControlColumnProps = Omit<
- EuiDataGridControlColumn,
- keyof AdditionalControlColumnProps
-> &
- Partial;
+import { ControlColumnProps } from '../../../../../../common/types/timeline';
+import { Actions } from '../actions';
+import { HeaderActions } from '../actions/header_actions';
export const defaultControlColumn: ControlColumnProps = {
id: 'default-timeline-control-column',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx
index ae6307c0a294b..ecacbc51e395a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx
@@ -36,11 +36,9 @@ describe('Columns', () => {
timelineId="test"
columnValues={'abc def'}
showCheckboxes={false}
- onPinEvent={jest.fn()}
selectedEventIds={{}}
loadingEventIds={[]}
onEventDetailsPanelOpened={jest.fn()}
- onUnPinEvent={jest.fn()}
onRowSelected={jest.fn()}
showNotes={false}
isEventPinned={false}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx
index ecabc3eae51c4..11bf88977fe61 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx
@@ -8,17 +8,20 @@
import { EuiScreenReaderOnly } from '@elastic/eui';
import React, { useMemo } from 'react';
import { getOr } from 'lodash/fp';
+import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '@kbn/securitysolution-t-grid';
-import { CellValueElementProps } from '../../cell_rendering';
-import { ControlColumnProps, RowCellRender } from '../control_columns';
-import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '../../../../../common/components/drag_and_drop/helpers';
import { Ecs } from '../../../../../../common/ecs';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
+import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ ActionProps,
+ ControlColumnProps,
+ TimelineTabs,
+ RowCellRender,
+} from '../../../../../../common/types/timeline';
import { ARIA_COLUMN_INDEX_OFFSET } from '../../helpers';
-import { OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events';
-import { ActionProps } from '../actions';
+import { OnRowSelected } from '../../events';
import { inputsModel } from '../../../../../common/store';
import {
EventsTd,
@@ -60,9 +63,7 @@ interface DataDrivenColumnProps {
loadingEventIds: Readonly;
notesCount: number;
onEventDetailsPanelOpened: () => void;
- onPinEvent: OnPinEvent;
onRowSelected: OnRowSelected;
- onUnPinEvent: OnUnPinEvent;
refetch: inputsModel.Refetch;
onRuleChange?: () => void;
hasRowRenderers: boolean;
@@ -137,9 +138,7 @@ const TgridActionTdCell = ({
loadingEventIds,
notesCount,
onEventDetailsPanelOpened,
- onPinEvent,
onRowSelected,
- onUnPinEvent,
refetch,
rowIndex,
hasRowRenderers,
@@ -193,9 +192,7 @@ const TgridActionTdCell = ({
isEventViewer={isEventViewer}
loadingEventIds={loadingEventIds}
onEventDetailsPanelOpened={onEventDetailsPanelOpened}
- onPinEvent={onPinEvent}
onRowSelected={onRowSelected}
- onUnPinEvent={onUnPinEvent}
refetch={refetch}
rowIndex={rowIndex}
onRuleChange={onRuleChange}
@@ -292,9 +289,7 @@ export const DataDrivenColumns = React.memo(
loadingEventIds,
notesCount,
onEventDetailsPanelOpened,
- onPinEvent,
onRowSelected,
- onUnPinEvent,
refetch,
hasRowRenderers,
onRuleChange,
@@ -345,8 +340,6 @@ export const DataDrivenColumns = React.memo(
isEventPinned={isEventPinned}
isEventViewer={isEventViewer}
notesCount={notesCount}
- onPinEvent={onPinEvent}
- onUnPinEvent={onUnPinEvent}
refetch={refetch}
hasRowRenderers={hasRowRenderers}
onRuleChange={onRuleChange}
@@ -365,7 +358,6 @@ export const DataDrivenColumns = React.memo(
data,
ecsData,
onRowSelected,
- onPinEvent,
isEventPinned,
isEventViewer,
actionsColumnWidth,
@@ -378,7 +370,6 @@ export const DataDrivenColumns = React.memo(
notesCount,
onEventDetailsPanelOpened,
onRuleChange,
- onUnPinEvent,
refetch,
selectedEventIds,
showCheckboxes,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.test.tsx
index 3c75bc7fb2649..3e22cba208ca2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.test.tsx
@@ -9,11 +9,13 @@ import { mount } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React, { useEffect } from 'react';
-import { CellValueElementProps } from '../../cell_rendering';
import { defaultHeaders, mockTimelineData } from '../../../../../common/mock';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
-import { ColumnHeaderOptions } from '../../../../store/timeline/model';
+import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
import { StatefulCell } from './stateful_cell';
import { getMappedNonEcsValue } from '.';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx
index a5f8336cc7997..7931e0739aa68 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx
@@ -7,10 +7,12 @@
import React, { HTMLAttributes, useState } from 'react';
-import { CellValueElementProps } from '../../cell_rendering';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
+import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
export interface CommonProps {
className?: string;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
index e56171aae003c..17f231c0fdad9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
@@ -60,9 +60,7 @@ describe('EventColumnView', () => {
loadingEventIds: [],
notesCount: 0,
onEventDetailsPanelOpened: jest.fn(),
- onPinEvent: jest.fn(),
onRowSelected: jest.fn(),
- onUnPinEvent: jest.fn(),
refetch: jest.fn(),
renderCellValue: DefaultCellRenderer,
selectedEventIds: {},
@@ -120,16 +118,6 @@ describe('EventColumnView', () => {
expect(wrapper.find('[data-test-subj="pin"]').exists()).toBe(false);
});
- test('it invokes onPinClicked when the button for pinning events is clicked', () => {
- const wrapper = mount( , { wrappingComponent: TestProviders });
-
- expect(props.onPinEvent).not.toHaveBeenCalled();
-
- wrapper.find('[data-test-subj="pin"]').first().simulate('click');
-
- expect(props.onPinEvent).toHaveBeenCalled();
- });
-
test('it render AddToCaseAction if timelineId === TimelineId.detectionsPage', () => {
const wrapper = mount( , {
wrappingComponent: TestProviders,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
index 5dc718f90a91a..298ce252ba925 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
@@ -7,16 +7,19 @@
import React, { useMemo } from 'react';
-import { CellValueElementProps } from '../../cell_rendering';
-import { ControlColumnProps, RowCellRender } from '../control_columns';
import { Ecs } from '../../../../../../common/ecs';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
-import { OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events';
+import { OnRowSelected } from '../../events';
import { EventsTrData, EventsTdGroupActions } from '../../styles';
import { DataDrivenColumns, getMappedNonEcsValue } from '../data_driven_columns';
import { inputsModel } from '../../../../../common/store';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
+import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ ControlColumnProps,
+ RowCellRender,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
interface Props {
id: string;
@@ -31,9 +34,7 @@ interface Props {
loadingEventIds: Readonly;
notesCount: number;
onEventDetailsPanelOpened: () => void;
- onPinEvent: OnPinEvent;
onRowSelected: OnRowSelected;
- onUnPinEvent: OnUnPinEvent;
refetch: inputsModel.Refetch;
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
onRuleChange?: () => void;
@@ -62,9 +63,7 @@ export const EventColumnView = React.memo(
loadingEventIds,
notesCount,
onEventDetailsPanelOpened,
- onPinEvent,
onRowSelected,
- onUnPinEvent,
refetch,
hasRowRenderers,
onRuleChange,
@@ -134,10 +133,8 @@ export const EventColumnView = React.memo(
eventIdToNoteIds={eventIdToNoteIds}
isEventPinned={isEventPinned}
isEventViewer={isEventViewer}
- onPinEvent={onPinEvent}
- onUnPinEvent={onUnPinEvent}
- refetch={refetch}
onRuleChange={onRuleChange}
+ refetch={refetch}
showNotes={showNotes}
tabType={tabType}
timelineId={timelineId}
@@ -161,10 +158,8 @@ export const EventColumnView = React.memo(
leadingControlColumns,
loadingEventIds,
onEventDetailsPanelOpened,
- onPinEvent,
onRowSelected,
onRuleChange,
- onUnPinEvent,
refetch,
selectedEventIds,
showCheckboxes,
@@ -201,8 +196,6 @@ export const EventColumnView = React.memo(
eventIdToNoteIds={eventIdToNoteIds}
isEventPinned={isEventPinned}
isEventViewer={isEventViewer}
- onPinEvent={onPinEvent}
- onUnPinEvent={onUnPinEvent}
refetch={refetch}
onRuleChange={onRuleChange}
selectedEventIds={selectedEventIds}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
index c3097ad68aba1..c09de87c87f32 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
@@ -8,19 +8,21 @@
import React from 'react';
import { isEmpty } from 'lodash';
-import { CellValueElementProps } from '../../cell_rendering';
-import { ControlColumnProps } from '../control_columns';
import { inputsModel } from '../../../../../common/store';
import { BrowserFields } from '../../../../../common/containers/source';
import {
TimelineItem,
TimelineNonEcsData,
} from '../../../../../../common/search_strategy/timeline';
-import { TimelineTabs } from '../../../../../../common/types/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
+import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ ControlColumnProps,
+ RowRenderer,
+ TimelineTabs,
+} from '../../../../../../common/types/timeline';
import { OnRowSelected } from '../../events';
import { EventsTbody } from '../../styles';
-import { RowRenderer } from '../renderers/row_renderer';
import { StatefulEvent } from './stateful_event';
import { eventIsPinned } from '../helpers';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
index 701dc549467e9..b8840a75cc9b4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
@@ -8,10 +8,12 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { CellValueElementProps } from '../../cell_rendering';
-import { ControlColumnProps } from '../control_columns';
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
import {
+ ColumnHeaderOptions,
+ CellValueElementProps,
+ ControlColumnProps,
+ RowRenderer,
TimelineExpandedDetailType,
TimelineId,
TimelineTabs,
@@ -21,11 +23,9 @@ import {
TimelineItem,
TimelineNonEcsData,
} from '../../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
-import { OnPinEvent, OnRowSelected } from '../../events';
+import { OnRowSelected } from '../../events';
import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers';
import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles';
-import { RowRenderer } from '../renderers/row_renderer';
import { isEventBuildingBlockType, getEventType, isEvenEqlSequence } from '../helpers';
import { NoteCards } from '../../../notes/note_cards';
import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context';
@@ -176,16 +176,6 @@ const StatefulEventComponent: React.FC = ({
});
}, [event]);
- const onPinEvent: OnPinEvent = useCallback(
- (eventId) => dispatch(timelineActions.pinEvent({ id: timelineId, eventId })),
- [dispatch, timelineId]
- );
-
- const onUnPinEvent: OnPinEvent = useCallback(
- (eventId) => dispatch(timelineActions.unPinEvent({ id: timelineId, eventId })),
- [dispatch, timelineId]
- );
-
const handleOnEventDetailPanelOpened = useCallback(() => {
const eventId = event._id;
const indexName = event._index!;
@@ -215,10 +205,10 @@ const StatefulEventComponent: React.FC = ({
(noteId: string) => {
dispatch(timelineActions.addNoteToEvent({ eventId: event._id, id: timelineId, noteId }));
if (!isEventPinned) {
- onPinEvent(event._id); // pin the event, because it has notes
+ dispatch(timelineActions.pinEvent({ id: timelineId, eventId: event._id }));
}
},
- [dispatch, event, isEventPinned, onPinEvent, timelineId]
+ [dispatch, event, isEventPinned, timelineId]
);
const RowRendererContent = useMemo(
@@ -273,9 +263,7 @@ const StatefulEventComponent: React.FC = ({
loadingEventIds={loadingEventIds}
notesCount={notes.length}
onEventDetailsPanelOpened={handleOnEventDetailPanelOpened}
- onPinEvent={onPinEvent}
onRowSelected={onRowSelected}
- onUnPinEvent={onUnPinEvent}
refetch={refetch}
renderCellValue={renderCellValue}
onRuleChange={onRuleChange}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
index 10a25538c1ba3..19abd6841e7e8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
@@ -9,15 +9,15 @@ import { noop } from 'lodash/fp';
import { EuiFocusTrap, EuiOutsideClickDetector, EuiScreenReaderOnly } from '@elastic/eui';
import React, { useMemo } from 'react';
-import { BrowserFields } from '../../../../../../common/containers/source';
import {
ARIA_COLINDEX_ATTRIBUTE,
ARIA_ROWINDEX_ATTRIBUTE,
getRowRendererClassName,
-} from '../../../../../../common/components/accessibility/helpers';
+} from '../../../../../../../../timelines/public';
+import { RowRenderer } from '../../../../../../../common';
+import { BrowserFields } from '../../../../../../common/containers/source';
import { TimelineItem } from '../../../../../../../common/search_strategy/timeline';
import { getRowRenderer } from '../../renderers/get_row_renderer';
-import { RowRenderer } from '../../renderers/row_renderer';
import { useStatefulEventFocus } from '../use_stateful_event_focus';
import * as i18n from '../translations';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx
index 5f3c4dac8b73d..4e8fd7dc48968 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx
@@ -13,7 +13,7 @@ import {
isEscape,
focusColumn,
OnColumnFocused,
-} from '../../../../../../common/components/accessibility/helpers';
+} from '../../../../../../../../timelines/public';
type FocusOwnership = 'not-owned' | 'owned';
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 61601c3921445..19059b5fb4599 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
@@ -23,6 +23,8 @@ import { timelineActions } from '../../../store/timeline';
import { TimelineTabs } from '../../../../../common/types/timeline';
import { defaultRowRenderers } from './renderers';
+jest.mock('../../../../common/lib/kibana');
+
const mockSort: Sort[] = [
{
columnId: '@timestamp',
@@ -255,7 +257,7 @@ describe('Body', () => {
tabType: 'query',
timelineId: 'timeline-test',
},
- type: 'x-pack/security_solution/local/timeline/TOGGLE_DETAIL_PANEL',
+ type: 'x-pack/timelines/t-grid/TOGGLE_DETAIL_PANEL',
});
});
@@ -279,7 +281,7 @@ describe('Body', () => {
tabType: 'pinned',
timelineId: 'timeline-test',
},
- type: 'x-pack/security_solution/local/timeline/TOGGLE_DETAIL_PANEL',
+ type: 'x-pack/timelines/t-grid/TOGGLE_DETAIL_PANEL',
});
});
@@ -303,7 +305,7 @@ describe('Body', () => {
tabType: 'notes',
timelineId: 'timeline-test',
},
- type: 'x-pack/security_solution/local/timeline/TOGGLE_DETAIL_PANEL',
+ type: 'x-pack/timelines/t-grid/TOGGLE_DETAIL_PANEL',
});
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx
index 64f61232377e8..fc8bf2086471c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx
@@ -11,21 +11,26 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { connect, ConnectedProps } from 'react-redux';
import deepEqual from 'fast-deep-equal';
-import { CellValueElementProps } from '../cell_rendering';
-import { DEFAULT_COLUMN_MIN_WIDTH } from './constants';
-import { ControlColumnProps } from './control_columns';
-import { RowRendererId, TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
import {
FIRST_ARIA_INDEX,
ARIA_COLINDEX_ATTRIBUTE,
ARIA_ROWINDEX_ATTRIBUTE,
onKeyDownFocusHandler,
-} from '../../../../common/components/accessibility/helpers';
+} from '../../../../../../timelines/public';
+import { CellValueElementProps } from '../cell_rendering';
+import { DEFAULT_COLUMN_MIN_WIDTH } from './constants';
+import {
+ ColumnHeaderOptions,
+ ControlColumnProps,
+ RowRendererId,
+ RowRenderer,
+ TimelineId,
+ TimelineTabs,
+} from '../../../../../common/types/timeline';
import { BrowserFields } from '../../../../common/containers/source';
import { TimelineItem } from '../../../../../common/search_strategy/timeline';
import { inputsModel, State } from '../../../../common/store';
-import { useManageTimeline } from '../../manage_timeline';
-import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model';
+import { TimelineModel } from '../../../store/timeline/model';
import { timelineDefaults } from '../../../store/timeline/defaults';
import { timelineActions, timelineSelectors } from '../../../store/timeline';
import { OnRowSelected, OnSelectAll } from '../events';
@@ -33,11 +38,11 @@ import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helper
import { getEventIdToDataMapping } from './helpers';
import { Sort } from './sort';
import { plainRowRenderer } from './renderers/plain_row_renderer';
-import { RowRenderer } from './renderers/row_renderer';
import { EventsTable, TimelineBody, TimelineBodyGlobalStyle } from '../styles';
import { ColumnHeaders } from './column_headers';
import { Events } from './events';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers';
+import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
interface OwnProps {
activePage: number;
@@ -99,11 +104,10 @@ export const BodyComponent = React.memo(
trailingControlColumns = [],
}) => {
const containerRef = useRef(null);
- const { getManageTimelineById } = useManageTimeline();
- const { queryFields, selectAll } = useMemo(() => getManageTimelineById(id), [
- getManageTimelineById,
- id,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { queryFields, selectAll } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, id)
+ );
const onRowSelected: OnRowSelected = useCallback(
({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx
index 21c44cb26e2e5..d5ec8b6f94862 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx
@@ -13,6 +13,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { TestProviders } from '../../../../../common/mock';
import { ArgsComponent } from './args';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx
index f45c049ca137a..2a5764e53756a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx
@@ -15,6 +15,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { AuditdGenericDetails, AuditdGenericLine } from './generic_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx
index 51676c067cd79..009ffecf28f74 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx
@@ -15,6 +15,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { AuditdGenericFileDetails, AuditdGenericFileLine } from './generic_file_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 31fea6fa25e65..74a5ff472b581 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
@@ -9,17 +9,19 @@ import { shallow } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
+import { RowRenderer } from '../../../../../../../common';
import { BrowserFields } from '../../../../../../common/containers/source';
import { mockBrowserFields } from '../../../../../../common/containers/source/mock';
import { Ecs } from '../../../../../../../common/ecs';
import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
-import { RowRenderer } from '../row_renderer';
import {
createGenericAuditRowRenderer,
createGenericFileRowRenderer,
} from './generic_row_renderer';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
index 9133e500162bc..765bfd3d21351 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
@@ -11,9 +11,9 @@ import { IconType } from '@elastic/eui';
import { get } from 'lodash/fp';
import React from 'react';
-import { RowRendererId } from '../../../../../../../common/types/timeline';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
import { AuditdGenericDetails } from './generic_details';
import { AuditdGenericFileDetails } from './generic_file_details';
import * as i18n from './translations';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx
index 24b9f8d40eb17..d6037a310dc7e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../../common/mock';
import { PrimarySecondaryUserInfo, nilOrUnSet } from './primary_secondary_user_info';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx
index 22cd8446a51c0..fa6eda6bce37d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx
@@ -14,6 +14,8 @@ import { TestProviders } from '../../../../../../common/mock';
import { SessionUserHostWorkingDir } from './session_user_host_working_dir';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx
index 8b4a9f72b1a45..c7da6f758766e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx
@@ -14,6 +14,8 @@ import { useMountAppended } from '../../../../../../common/utils/use_mount_appen
import { Bytes } from '.';
+jest.mock('../../../../../../common/lib/kibana');
+
describe('Bytes', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
index cb670b53a9679..65bb67458ab2a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
@@ -6,8 +6,8 @@
*/
import type React from 'react';
+import { ColumnHeaderOptions } from '../../../../../../common';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
export interface ColumnRenderer {
isInstance: (columnName: string, data: TimelineNonEcsData[]) => boolean;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.test.tsx
index 7f580642130fe..872ca017d7f7d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.test.tsx
@@ -12,6 +12,8 @@ import { TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { ThreatMatchRowProps, ThreatMatchRowView } from './threat_match_row';
+jest.mock('../../../../../../common/lib/kibana');
+
describe('ThreatMatchRowView', () => {
const mount = useMountAppended();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx
index 2a7e8ce02d79f..16426bf74aba7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx
@@ -5,8 +5,7 @@
* 2.0.
*/
-import { RowRendererId } from '../../../../../../../common/types/timeline';
-import { RowRenderer } from '../row_renderer';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
import { hasThreatMatchValue } from './helpers';
import { ThreatMatchRows } from './threat_match_rows';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_rows.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_rows.tsx
index cc34f9e63b5e2..f6feb6dd1b126 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_rows.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_rows.tsx
@@ -10,9 +10,10 @@ import { get } from 'lodash';
import React, { Fragment } from 'react';
import styled from 'styled-components';
+import { RowRenderer } from '../../../../../../../common';
import { Fields } from '../../../../../../../common/search_strategy';
import { ID_FIELD_NAME } from '../../../../../../common/components/event_details/event_id';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
import { ThreatMatchRow } from './threat_match_row';
const SpacedContainer = styled.div`
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx
index d3e870aa92ef0..9e6c5b819a20b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx
@@ -15,6 +15,8 @@ import { useMountAppended } from '../../../../../../common/utils/use_mount_appen
import { DnsRequestEventDetails } from './dns_request_event_details';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx
index 2809b06c77469..5c0aecf5fbbc7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx
@@ -12,6 +12,8 @@ import '../../../../../../common/mock/match_media';
import { DnsRequestEventDetailsLine } from './dns_request_event_details_line';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx
index 034ade75ef2c0..5144705f26174 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx
@@ -18,6 +18,8 @@ import { getEmptyValue } from '../../../../../common/components/empty_value';
import { deleteItemIdx, findItem } from './helpers';
import { emptyColumnRenderer } from './empty_column_renderer';
+jest.mock('../../../../../common/lib/kibana');
+
describe('empty_column_renderer', () => {
let mockDatum: TimelineNonEcsData[];
const _id = mockTimelineData[0]._id;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.tsx
index 400ccf47201ac..37873df7f4e7b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.tsx
@@ -8,9 +8,8 @@
/* eslint-disable react/display-name */
import React from 'react';
-
+import { ColumnHeaderOptions } from '../../../../../../common';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import {
DraggableWrapper,
DragEffects,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx
index c1df6d6eb48c8..613d66505601a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx
@@ -20,6 +20,8 @@ import { useMountAppended } from '../../../../../../common/utils/use_mount_appen
import { EndgameSecurityEventDetails } from './endgame_security_event_details';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx
index 5d08898789821..879862d06b250 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx
@@ -13,6 +13,8 @@ import '../../../../../../common/mock/match_media';
import { EndgameSecurityEventDetailsLine } from './endgame_security_event_details_line';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx
index a6f15a9f79f4e..1bf8d1a4a4f51 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx
@@ -13,6 +13,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { ExitCodeDraggable } from './exit_code_draggable';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
index d7274f0774fc5..cf3fce2c25c0b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../common/mock';
import { FileDraggable } from './file_draggable';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
index e7e6274942bea..8ebd3ae8a67c2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
@@ -13,6 +13,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { FileHash } from './file_hash';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx
index 8e54f13ec9cbf..852331aa021dd 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx
@@ -21,6 +21,8 @@ import { getColumnRenderer } from './get_column_renderer';
import { getValues, findItem, deleteItemIdx } from './helpers';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 56dbc99d47c66..104550f138f16 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
@@ -20,6 +20,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { defaultRowRenderers } from '.';
import { getRowRenderer } from './get_row_renderer';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts
index bfe60a14e042d..2d1be6ee7914a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { RowRenderer } from '../../../../../../common';
import { Ecs } from '../../../../../../common/ecs';
-import { RowRenderer } from './row_renderer';
export const getRowRenderer = (ecs: Ecs, rowRenderers: RowRenderer[]): RowRenderer | null =>
rowRenderers.find((rowRenderer) => rowRenderer.isInstance(ecs)) ?? null;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx
index 9412ecfd364ba..d650710b25cad 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx
@@ -13,6 +13,8 @@ import { mockTimelineData, TestProviders } from '../../../../../common/mock';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
import { HostWorkingDir } from './host_working_dir';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
index 537a24bbfd953..911dcc8cd2e87 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
@@ -5,12 +5,12 @@
* 2.0.
*/
+import { RowRenderer } from '../../../../../../common';
import { auditdRowRenderers } from './auditd/generic_row_renderer';
import { ColumnRenderer } from './column_renderer';
import { emptyColumnRenderer } from './empty_column_renderer';
import { netflowRowRenderer } from './netflow/netflow_row_renderer';
import { plainColumnRenderer } from './plain_column_renderer';
-import { RowRenderer } from './row_renderer';
import { suricataRowRenderer } from './suricata/suricata_row_renderer';
import { unknownColumnRenderer } from './unknown_column_renderer';
import { zeekRowRenderer } from './zeek/zeek_row_renderer';
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 72e3516827c8a..fc97624dbfc96 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
@@ -26,6 +26,8 @@ export const justIdAndTimestamp: Ecs = {
timestamp: '2018-11-12T19:03:25.936Z',
};
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('../../../../../../common/components/link_to');
describe('netflowRowRenderer', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
index 2605670ee8b38..35406dce6ff72 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
@@ -11,7 +11,7 @@ import { get } from 'lodash/fp';
import React from 'react';
import styled from 'styled-components';
-import { RowRendererId } from '../../../../../../../common/types/timeline';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
import { asArrayIfExists } from '../../../../../../common/lib/helpers';
import {
TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME,
@@ -63,7 +63,7 @@ import {
SOURCE_BYTES_FIELD_NAME,
SOURCE_PACKETS_FIELD_NAME,
} from '../../../../../../network/components/source_destination/source_destination_arrows';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
const Details = styled.div`
margin: 5px 0;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx
index 2402be88dea18..7c28747cc84ef 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../common/mock';
import { ParentProcessDraggable } from './parent_process_draggable';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 a56acbe48685c..e970aaad026b1 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
@@ -18,6 +18,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { plainColumnRenderer } from './plain_column_renderer';
import { getValues, deleteItemIdx, findItem } from './helpers';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.tsx
index a2b7750d9bb59..77039ddc4a586 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.tsx
@@ -8,8 +8,8 @@
import { head } from 'lodash/fp';
import React from 'react';
+import { ColumnHeaderOptions } from '../../../../../../common';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
import { ColumnRenderer } from './column_renderer';
import { FormattedFieldValue } from './formatted_field';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
index 0b5afd579d08c..15620a7fc04b4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
@@ -7,9 +7,7 @@
import React from 'react';
-import { RowRendererId } from '../../../../../../common/types/timeline';
-
-import { RowRenderer } from './row_renderer';
+import { RowRendererId, RowRenderer } from '../../../../../../common/types/timeline';
const PlainRowRenderer = () => <>>;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx
index 31a1745fa2a6d..6509808fb0c9f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx
@@ -13,6 +13,8 @@ import '../../../../../common/mock/match_media';
import { ProcessDraggable, ProcessDraggableWithNonExistentProcess } from './process_draggable';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
index 9e90e061e94d5..7135f2a5fed6a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
@@ -13,6 +13,8 @@ import { useMountAppended } from '../../../../../common/utils/use_mount_appended
import { ProcessHash } from './process_hash';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
index f37adef7e73cb..e5bb91c532505 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
@@ -18,6 +18,8 @@ import { MODIFIED_REGISTRY_KEY } from '../system/translations';
import { RegistryEventDetails } from './registry_event_details';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
index 6be1529152523..d0287f2b010ae 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
@@ -13,6 +13,8 @@ import { useMountAppended } from '../../../../../../common/utils/use_mount_appen
import { RegistryEventDetailsLine } from './registry_event_details_line';
import { MODIFIED_REGISTRY_KEY } from '../system/translations';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
index 679da28e622bf..9099f76b8305c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
@@ -7,11 +7,7 @@
import React from 'react';
-import { BrowserFields } from '../../../../../common/containers/source';
-import type { RowRendererId } from '../../../../../../common/types/timeline';
-import { Ecs } from '../../../../../../common/ecs';
import { EventsTrSupplement } from '../../styles';
-
interface RowRendererContainerProps {
children: React.ReactNode;
}
@@ -22,17 +18,3 @@ export const RowRendererContainer = React.memo(({ chi
));
RowRendererContainer.displayName = 'RowRendererContainer';
-
-export interface RowRenderer {
- id: RowRendererId;
- isInstance: (data: Ecs) => boolean;
- renderRow: ({
- browserFields,
- data,
- timelineId,
- }: {
- browserFields: BrowserFields;
- data: Ecs;
- timelineId: string;
- }) => React.ReactNode;
-}
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 5960f43174b98..355077ee50066 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
@@ -16,6 +16,8 @@ import { TestProviders } from '../../../../../../common/mock/test_providers';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { SuricataDetails } from './suricata_details';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 098d6775cfaa4..998233b2278c9 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
@@ -18,6 +18,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/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
index 5a68bc6fe28c8..aa482926bf007 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
@@ -10,9 +10,9 @@
import { get } from 'lodash/fp';
import React from 'react';
-import { RowRendererId } from '../../../../../../../common/types/timeline';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
import { SuricataDetails } from './suricata_details';
export const suricataRowRenderer: RowRenderer = {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx
index 4a727e4e7bc27..b3911f9eded67 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx
@@ -18,6 +18,8 @@ import {
SURICATA_SIGNATURE_ID_FIELD_NAME,
} from './suricata_signature';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 001b7f4b68bab..35872d0093f02 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
@@ -15,6 +15,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { SystemGenericDetails, SystemGenericLine } from './generic_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 b660d823954ee..f5dc4c6fdf599 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
@@ -16,6 +16,8 @@ import { mockEndgameCreationEvent } from '../../../../../../common/mock/mock_end
import { SystemGenericFileDetails, SystemGenericFileLine } from './generic_file_details';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 8e8ce9cb2f988..6f5b225f0690b 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
@@ -67,7 +67,6 @@ import {
mockEndpointSecurityLogOffEvent,
} from '../../../../../../common/mock/mock_endgame_ecs_data';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
-import { RowRenderer } from '../row_renderer';
import {
createDnsRowRenderer,
createEndgameProcessRowRenderer,
@@ -82,6 +81,9 @@ import {
EndpointAlertCriteria,
} from './generic_row_renderer';
import * as i18n from './translations';
+import { RowRenderer } from '../../../../../../../common';
+
+jest.mock('../../../../../../common/lib/kibana');
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
index 211fa9152dc8d..c6845d7d672d2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
@@ -10,13 +10,13 @@
import { get } from 'lodash/fp';
import React from 'react';
-import { RowRendererId } from '../../../../../../../common/types/timeline';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
import { DnsRequestEventDetails } from '../dns/dns_request_event_details';
import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details';
import { isFileEvent, isNillEmptyOrNotFinite } from '../helpers';
import { RegistryEventDetails } from '../registry/registry_event_details';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
import { SystemGenericDetails } from './generic_details';
import { SystemGenericFileDetails } from './generic_file_details';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx
index ac1e4d6748dcd..be11955169bd7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx
@@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { Package } from './package';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx
index dfb9ae69ac2d4..7cff1166cd0de 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx
@@ -13,6 +13,8 @@ import '../../../../../common/mock/match_media';
import { UserHostWorkingDir } from './user_host_working_dir';
import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+jest.mock('../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 04150163fb4d4..7f0ec8b7b0b79 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
@@ -14,6 +14,8 @@ import { mockTimelineData, TestProviders } from '../../../../../../common/mock';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { ZeekDetails } from './zeek_details';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
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 749e450b36ae4..6b154d4d32707 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
@@ -17,6 +17,8 @@ import '../../../../../../common/mock/match_media';
import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
import { zeekRowRenderer } from './zeek_row_renderer';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
index 7a8d284d0ec1e..2b6311b8cae83 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
@@ -10,9 +10,9 @@
import { get } from 'lodash/fp';
import React from 'react';
-import { RowRendererId } from '../../../../../../../common/types/timeline';
+import { RowRendererId, RowRenderer } from '../../../../../../../common/types/timeline';
-import { RowRenderer, RowRendererContainer } from '../row_renderer';
+import { RowRendererContainer } from '../row_renderer';
import { ZeekDetails } from './zeek_details';
export const zeekRowRenderer: RowRenderer = {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx
index 61155331b1a4b..28034dac8f575 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx
@@ -28,6 +28,8 @@ import {
defaultStringRenderer,
} from './zeek_signature';
+jest.mock('../../../../../../common/lib/kibana');
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/index.ts
index e7c69b9229d70..bd05bf0656687 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/index.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/index.ts
@@ -5,15 +5,7 @@
* 2.0.
*/
-import { Direction } from '../../../../../../common/search_strategy';
-import { ColumnId } from '../column_id';
-
-/** Specifies a column's sort direction */
-export type SortDirection = 'none' | Direction;
+import { SortColumnTimeline } from '../../../../../../common/types/timeline';
/** Specifies which column the timeline is sorted on */
-export interface Sort {
- columnId: ColumnId;
- columnType: string;
- sortDirection: SortDirection;
-}
+export type Sort = SortColumnTimeline;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx
index 6af29793f9373..3e610abe79050 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx
@@ -11,8 +11,8 @@ import React from 'react';
import * as i18n from '../translations';
import { SortNumber } from './sort_number';
-import { SortDirection } from '.';
import { Direction } from '../../../../../../common/search_strategy';
+import { SortDirection } from '../../../../../../common/types/timeline';
enum SortDirectionIndicatorEnum {
SORT_UP = 'sortUp',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx
index 5ac1dcf8805cf..06d8133a24f6e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx
@@ -17,6 +17,8 @@ import { mockBrowserFields } from '../../../../common/containers/source/mock';
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock';
import { DefaultCellRenderer } from './default_cell_renderer';
+jest.mock('../../../../common/lib/kibana');
+
jest.mock('../body/renderers/get_column_renderer');
const getColumnRendererMock = getColumnRenderer as jest.Mock;
const mockImplementation = {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx
index 03e444e3a9afd..2848a850a5227 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx
@@ -5,16 +5,4 @@
* 2.0.
*/
-import { EuiDataGridCellValueElementProps } from '@elastic/eui';
-
-import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline';
-import { ColumnHeaderOptions } from '../../../store/timeline/model';
-
-/** The following props are provided to the function called by `renderCellValue` */
-export type CellValueElementProps = EuiDataGridCellValueElementProps & {
- data: TimelineNonEcsData[];
- eventId: string; // _id
- header: ColumnHeaderOptions;
- linkValues: string[] | undefined;
- timelineId: string;
-};
+export { CellValueElementProps } from '../../../../../common/types/timeline';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx
index 35595de646126..ef04c1177dcd6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx
@@ -11,11 +11,6 @@ import { TestProviders } from '../../../../common/mock/test_providers';
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
import { DataProviders } from '.';
-import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
-import { FilterManager } from '../../../../../../../../src/plugins/data/public/query/filter_manager';
-import { coreMock } from '../../../../../../../../src/core/public/mocks';
-
-const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
jest.mock('../../../../common/hooks/use_selector', () => {
const actual = jest.requireActual('../../../../common/hooks/use_selector');
@@ -25,7 +20,6 @@ jest.mock('../../../../common/hooks/use_selector', () => {
};
});
-const filterManager = new FilterManager(mockUiSettingsForFilterManager);
describe('DataProviders', () => {
const mount = useMountAppended();
@@ -33,17 +27,9 @@ describe('DataProviders', () => {
const dropMessage = ['Drop', 'query', 'build', 'here'];
test('renders correctly against snapshot', () => {
- const manageTimelineForTesting = {
- foo: {
- ...getTimelineDefaults('foo'),
- filterManager,
- },
- };
const wrapper = mount(
-
-
-
+
);
expect(wrapper.find(`[data-test-subj="dataProviders-container"]`)).toBeTruthy();
@@ -73,19 +59,10 @@ describe('DataProviders', () => {
});
describe('resizable drop target', () => {
- const manageTimelineForTesting = {
- foo: {
- ...getTimelineDefaults('test'),
- filterManager,
- },
- };
-
test('it may be resized vertically via a resize handle', () => {
const wrapper = mount(
-
-
-
+
);
@@ -98,9 +75,7 @@ describe('DataProviders', () => {
test('it never grows taller than one third (33%) of the view height', () => {
const wrapper = mount(
-
-
-
+
);
@@ -113,9 +88,7 @@ describe('DataProviders', () => {
test('it automatically displays scroll bars when the width or height of the data providers exceeds the drop target', () => {
const wrapper = mount(
-
-
-
+
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx
index bdc0327026488..f642ec35d4306 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx
@@ -9,19 +9,16 @@ import { rgba } from 'polished';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import uuid from 'uuid';
+import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
import { useSourcererScope } from '../../../../common/containers/sourcerer';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import { DroppableWrapper } from '../../../../common/components/drag_and_drop/droppable_wrapper';
-import {
- droppableTimelineProvidersPrefix,
- IS_DRAGGING_CLASS_NAME,
-} from '../../../../common/components/drag_and_drop/helpers';
+import { droppableTimelineProvidersPrefix } from '../../../../common/components/drag_and_drop/helpers';
import { Empty } from './empty';
import { Providers } from './providers';
-import { useManageTimeline } from '../../manage_timeline';
import { timelineSelectors } from '../../../store/timeline';
import { timelineDefaults } from '../../../store/timeline/defaults';
@@ -89,11 +86,8 @@ const getDroppableId = (id: string): string =>
*/
export const DataProviders = React.memo(({ timelineId }) => {
const { browserFields } = useSourcererScope(SourcererScopeName.timeline);
- const { getManageTimelineById } = useManageTimeline();
- const isLoading = useMemo(() => getManageTimelineById(timelineId).isLoading, [
- getManageTimelineById,
- timelineId,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { isLoading } = useDeepEqualSelector((state) => getManageTimeline(state, timelineId));
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const dataProviders = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).dataProviders
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
index a3693d5ba2001..e5e5ad5f010fc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
@@ -11,7 +11,10 @@ import { useDispatch } from 'react-redux';
import { TimelineType } from '../../../../../common/types/timeline';
import { BrowserFields } from '../../../../common/containers/source';
-import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
+import {
+ useDeepEqualSelector,
+ useShallowEqualSelector,
+} from '../../../../common/hooks/use_selector';
import { timelineSelectors } from '../../../store/timeline';
import { OnDataProviderEdited } from '../events';
@@ -19,7 +22,6 @@ import { ProviderBadge } from './provider_badge';
import { ProviderItemActions } from './provider_item_actions';
import { DataProvidersAnd, DataProviderType, QueryOperator } from './data_provider';
import { dragAndDropActions } from '../../../../common/store/drag_and_drop';
-import { useManageTimeline } from '../../manage_timeline';
interface ProviderItemBadgeProps {
andProviderId?: string;
@@ -75,11 +77,10 @@ export const ProviderItemBadge = React.memo(
return getTimeline(state, timelineId)?.timelineType ?? TimelineType.default;
});
- const { getManageTimelineById } = useManageTimeline();
- const isLoading = useMemo(() => getManageTimelineById(timelineId ?? '').isLoading, [
- getManageTimelineById,
- timelineId,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { isLoading } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId ?? '')
+ );
const togglePopover = useCallback(() => {
setIsPopoverOpen(!isPopoverOpen);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.test.tsx
index 7f2133aca7348..a2a91c206521a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.test.tsx
@@ -8,36 +8,30 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { coreMock } from '../../../../../../../../src/core/public/mocks';
import { TestProviders } from '../../../../common/mock/test_providers';
import { DroppableWrapper } from '../../../../common/components/drag_and_drop/droppable_wrapper';
-import { FilterManager } from '../../../../../../../../src/plugins/data/public';
import { timelineActions } from '../../../store/timeline';
import { mockDataProviders } from './mock/mock_data_providers';
import { Providers } from './providers';
import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions';
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
-import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
+import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
-const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
+jest.mock('../../../../common/lib/kibana');
+
+jest.mock('../../../../common/hooks/use_selector', () => ({
+ useShallowEqualSelector: jest.fn(),
+ useDeepEqualSelector: jest.fn(),
+}));
describe('Providers', () => {
- const isLoading: boolean = true;
const mount = useMountAppended();
- const filterManager = new FilterManager(mockUiSettingsForFilterManager);
const mockOnDataProviderRemoved = jest.spyOn(timelineActions, 'removeProvider');
- const manageTimelineForTesting = {
- test: {
- ...getTimelineDefaults('test'),
- filterManager,
- isLoading,
- },
- };
-
beforeEach(() => {
jest.clearAllMocks();
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: false });
});
describe('rendering', () => {
@@ -82,13 +76,12 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when the close button is clicked', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const wrapper = mount(
-
-
-
-
-
+
+
+
);
@@ -120,13 +113,12 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const wrapper = mount(
-
-
-
-
-
+
+
+
);
wrapper.find('button[data-test-subj="providerBadge"]').first().simulate('click');
@@ -172,17 +164,16 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const mockOnToggleDataProviderEnabled = jest.spyOn(
timelineActions,
'updateDataProviderEnabled'
);
const wrapper = mount(
-
-
-
-
-
+
+
+
);
@@ -231,6 +222,7 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const mockOnToggleDataProviderExcluded = jest.spyOn(
timelineActions,
'updateDataProviderExcluded'
@@ -238,11 +230,9 @@ describe('Providers', () => {
const wrapper = mount(
-
-
-
-
-
+
+
+
);
@@ -311,16 +301,15 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the close button is clicked', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const wrapper = mount(
-
-
-
-
-
+
+
+
);
@@ -375,6 +364,7 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderEnabled = jest.spyOn(
@@ -384,11 +374,9 @@ describe('Providers', () => {
const wrapper = mount(
-
-
-
-
-
+
+
+
);
@@ -448,6 +436,7 @@ describe('Providers', () => {
});
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
+ (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderExcluded = jest.spyOn(
@@ -457,11 +446,9 @@ describe('Providers', () => {
const wrapper = mount(
-
-
-
-
-
+
+
+
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx
index d7436d2b891b8..d144a67c27509 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx
@@ -13,17 +13,18 @@ import styled from 'styled-components';
import { useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
+import {
+ DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME,
+ IS_DRAGGING_CLASS_NAME,
+} from '@kbn/securitysolution-t-grid';
import { timelineActions } from '../../../store/timeline';
import { AndOrBadge } from '../../../../common/components/and_or_badge';
-import { useDraggableKeyboardWrapper } from '../../../../common/components/drag_and_drop/draggable_keyboard_wrapper_hook';
import { AddDataProviderPopover } from './add_data_provider_popover';
import { BrowserFields } from '../../../../common/containers/source';
import {
- DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME,
getTimelineProviderDraggableId,
getTimelineProviderDroppableId,
- IS_DRAGGING_CLASS_NAME,
} from '../../../../common/components/drag_and_drop/helpers';
import { DataProvider, DataProviderType, DataProvidersAnd, IS_OPERATOR } from './data_provider';
@@ -31,6 +32,7 @@ import { EMPTY_GROUP, flattenIntoAndGroups } from './helpers';
import { ProviderItemBadge } from './provider_item_badge';
import * as i18n from './translations';
+import { useKibana } from '../../../../common/lib/kibana';
export const EMPTY_PROVIDERS_GROUP_CLASS_NAME = 'empty-providers-group';
@@ -159,6 +161,7 @@ export const DataProvidersGroupItem = React.memo(
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [, setClosePopOverTrigger] = useState(false);
const dispatch = useDispatch();
+ const { timelines } = useKibana().services;
const handleClosePopOverTrigger = useCallback(() => {
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
@@ -244,7 +247,7 @@ export const DataProvidersGroupItem = React.memo(
setIsPopoverOpen(true);
}, []);
- const { onBlur, onKeyDown } = useDraggableKeyboardWrapper({
+ const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
closePopover: handleClosePopOverTrigger,
draggableId,
fieldName: dataProvider.queryMatch.field,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx
index e13bed1e2eff6..5f08bf5a016f5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx
@@ -22,6 +22,7 @@ import { useTimelineEvents } from '../../../containers/index';
import { useTimelineEventsDetails } from '../../../containers/details/index';
import { useSourcererScope } from '../../../../common/containers/sourcerer';
import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks';
+import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components';
jest.mock('../../../containers/index', () => ({
useTimelineEvents: jest.fn(),
@@ -57,6 +58,10 @@ jest.mock('../../../../common/lib/kibana', () => {
savedObjects: {
client: {},
},
+ timelines: {
+ getLastUpdated: jest.fn(),
+ getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
+ },
},
}),
useGetUserSavedObjectPermissions: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx
index bb2a995ff9fae..b67b9348f51aa 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx
@@ -17,7 +17,7 @@ import { isEmpty } from 'lodash/fp';
import React, { useEffect, useCallback } from 'react';
import styled from 'styled-components';
import { Dispatch } from 'redux';
-import { connect, ConnectedProps } from 'react-redux';
+import { connect, ConnectedProps, useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import { InPortal } from 'react-reverse-portal';
@@ -27,12 +27,17 @@ import { TimelineItem } from '../../../../../common/search_strategy';
import { useTimelineEvents } from '../../../containers/index';
import { defaultHeaders } from '../body/column_headers/default_headers';
import { StatefulBody } from '../body';
-import { RowRenderer } from '../body/renderers/row_renderer';
import { Footer, footerHeight } from '../footer';
import { calculateTotalPages } from '../helpers';
import { TimelineRefetch } from '../refetch_timeline';
-import { useManageTimeline } from '../../manage_timeline';
-import { TimelineEventsType, TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
+import {
+ ControlColumnProps,
+ RowRenderer,
+ TimelineEventsType,
+ TimelineId,
+ TimelineTabs,
+ ToggleDetailPanel,
+} from '../../../../../common/types/timeline';
import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config';
import { ExitFullScreen } from '../../../../common/components/exit_full_screen';
import { SuperDatePicker } from '../../../../common/components/super_date_picker';
@@ -48,10 +53,9 @@ import { TimelineModel } from '../../../../timelines/store/timeline/model';
import { TimelineDatePickerLock } from '../date_picker_lock';
import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen';
import { activeTimeline } from '../../../containers/active_timeline_context';
-import { ToggleDetailPanel } from '../../../store/timeline/actions';
import { DetailsPanel } from '../../side_panel';
import { EqlQueryBarTimeline } from '../query_bar/eql';
-import { defaultControlColumn, ControlColumnProps } from '../body/control_columns';
+import { defaultControlColumn } from '../body/control_columns';
import { Sort } from '../body/sort';
const TimelineHeaderContainer = styled.div`
@@ -166,6 +170,7 @@ export const EqlTabContentComponent: React.FC = ({
timerangeKind,
updateEventTypeAndIndexesName,
}) => {
+ const dispatch = useDispatch();
const { query: eqlQuery = '', ...restEqlOption } = eqlOptions;
const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal();
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
@@ -192,12 +197,13 @@ export const EqlTabContentComponent: React.FC = ({
return [...columnFields, ...requiredFieldsForActions];
};
- const { initializeTimeline, setIsTimelineLoading } = useManageTimeline();
useEffect(() => {
- initializeTimeline({
- id: timelineId,
- });
- }, [initializeTimeline, timelineId]);
+ dispatch(
+ timelineActions.initializeTGridSettings({
+ id: timelineId,
+ })
+ );
+ }, [dispatch, timelineId]);
const [
isQueryLoading,
@@ -230,8 +236,13 @@ export const EqlTabContentComponent: React.FC = ({
}, [onEventClosed, timelineId, expandedDetail, showExpandedDetails]);
useEffect(() => {
- setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer });
- }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]);
+ dispatch(
+ timelineActions.updateIsLoading({
+ id: timelineId,
+ isLoading: isQueryLoading || loadingSourcerer,
+ })
+ );
+ }, [loadingSourcerer, timelineId, isQueryLoading, dispatch]);
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
const trailingControlColumns: ControlColumnProps[] = [];
@@ -385,7 +396,6 @@ const makeMapStateToProps = () => {
};
return mapStateToProps;
};
-
const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({
updateEventTypeAndIndexesName: (newEventType: TimelineEventsType, newIndexNames: string[]) => {
dispatch(timelineActions.updateEventType({ id: timelineId, eventType: newEventType }));
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts
index 21e213b799535..ca7c3596d13bb 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts
@@ -5,10 +5,20 @@
* 2.0.
*/
-import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { ColumnId } from './body/column_id';
-import { SortDirection } from './body/sort';
import { DataProvider, QueryOperator } from './data_providers/data_provider';
+export {
+ OnColumnSorted,
+ OnColumnsSorted,
+ OnColumnRemoved,
+ OnColumnResized,
+ OnChangePage,
+ OnPinEvent,
+ OnRowSelected,
+ OnSelectAll,
+ OnUnPinEvent,
+ OnUpdateColumns,
+} from '../../../../common/types/timeline';
export type OnDataProviderEdited = ({
andProviderId,
@@ -35,38 +45,3 @@ export type OnRangeSelected = (range: string) => void;
/** Invoked when a user updates a column's filter */
export type OnFilterChange = (filter: { columnId: ColumnId; filter: string }) => void;
-
-/** Invoked when a column is sorted */
-export type OnColumnSorted = (sorted: { columnId: ColumnId; sortDirection: SortDirection }) => void;
-
-export type OnColumnsSorted = (
- sorted: Array<{ columnId: ColumnId; sortDirection: SortDirection }>
-) => void;
-
-export type OnColumnRemoved = (columnId: ColumnId) => void;
-
-export type OnColumnResized = ({ columnId, delta }: { columnId: ColumnId; delta: number }) => void;
-
-/** Invoked when a user clicks to load more item */
-export type OnChangePage = (nextPage: number) => void;
-
-/** Invoked when a user pins an event */
-export type OnPinEvent = (eventId: string) => void;
-
-/** Invoked when a user checks/un-checks a row */
-export type OnRowSelected = ({
- eventIds,
- isSelected,
-}: {
- eventIds: string[];
- isSelected: boolean;
-}) => void;
-
-/** Invoked when a user checks/un-checks the select all checkbox */
-export type OnSelectAll = ({ isSelected }: { isSelected: boolean }) => void;
-
-/** Invoked when columns are updated */
-export type OnUpdateColumns = (columns: ColumnHeaderOptions[]) => void;
-
-/** Invoked when a user unpins an event */
-export type OnUnPinEvent = (eventId: string) => void;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.test.tsx
index f0a14e990e1cc..cf8d51546a899 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.test.tsx
@@ -12,6 +12,8 @@ import { TestProviders } from '../../../../common/mock/test_providers';
import { FooterComponent, PagingControlComponent } from './index';
+jest.mock('../../../../common/lib/kibana');
+
describe('Footer Timeline Component', () => {
const loadMore = jest.fn();
const updatedAt = 1546878704036;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
index 4c5432f686c93..ac6f6e52db1e2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
@@ -24,15 +24,14 @@ import React, { FC, useCallback, useEffect, useState, useMemo } from 'react';
import styled from 'styled-components';
import { useDispatch } from 'react-redux';
-import { LoadingPanel } from '../../loading';
import { OnChangePage } from '../events';
import { EVENTS_COUNT_BUTTON_CLASS_NAME } from '../helpers';
import * as i18n from './translations';
import { useEventDetailsWidthContext } from '../../../../common/components/events_viewer/event_details_width_context';
-import { useManageTimeline } from '../../manage_timeline';
-import { LastUpdatedAt } from '../../../../common/components/last_updated';
-import { timelineActions } from '../../../store/timeline';
+import { timelineActions, timelineSelectors } from '../../../store/timeline';
+import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
+import { useKibana } from '../../../../common/lib/kibana';
export const isCompactFooter = (width: number): boolean => width < 600;
@@ -42,12 +41,13 @@ interface FixedWidthLastUpdatedContainerProps {
const FixedWidthLastUpdatedContainer = React.memo(
({ updatedAt }) => {
+ const { timelines } = useKibana().services;
const width = useEventDetailsWidthContext();
const compact = useMemo(() => isCompactFooter(width), [width]);
return (
-
+ {timelines.getLastUpdated({ updatedAt, compact })}
);
}
@@ -259,14 +259,16 @@ export const FooterComponent = ({
totalCount,
}: FooterProps) => {
const dispatch = useDispatch();
+ const { timelines } = useKibana().services;
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [paginationLoading, setPaginationLoading] = useState(false);
- const { getManageTimelineById } = useManageTimeline();
- const { documentType, loadingText, footerText } = useMemo(() => getManageTimelineById(id), [
- getManageTimelineById,
- id,
- ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const {
+ documentType = i18n.TOTAL_COUNT_OF_EVENTS,
+ loadingText = i18n.LOADING_EVENTS,
+ footerText = i18n.TOTAL_COUNT_OF_EVENTS,
+ } = useDeepEqualSelector((state) => getManageTimeline(state, id));
const handleChangePageClick = useCallback(
(nextPage: number) => {
@@ -322,13 +324,13 @@ export const FooterComponent = ({
if (isLoading && !paginationLoading) {
return (
-
+ {timelines.getLoadingPanel({
+ dataTestSubj: 'LoadingPanelTimeline',
+ height: '35px',
+ showBorder: false,
+ text: loadingText,
+ width: '100%',
+ })}
);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts
index fa8a8b743646d..6736573cac293 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts
@@ -43,3 +43,10 @@ export const AUTO_REFRESH_ACTIVE = i18n.translate(
defaultMessage: 'Auto-Refresh Active',
}
);
+
+export const LOADING_EVENTS = i18n.translate(
+ 'xpack.securitySolution.footer.loadingEventsDataLabel',
+ {
+ defaultMessage: 'Loading Events',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
index 0093ce2f95bdd..f2a4071111602 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
@@ -14,7 +14,7 @@ import {
getFocusedAriaColindexCell,
getTableSkipFocus,
stopPropagationAndPreventDefault,
-} from '../../../common/components/accessibility/helpers';
+} from '../../../../../timelines/public';
import { escapeQueryValue, convertToBuildEsQuery } from '../../../common/lib/keury';
import {
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 5e86bf8d75385..e95efdf754418 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
@@ -11,16 +11,15 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
+import { isTab } from '../../../../../timelines/public';
import { timelineActions, timelineSelectors } from '../../store/timeline';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { defaultHeaders } from './body/column_headers/default_headers';
-import { RowRenderer } from './body/renderers/row_renderer';
import { CellValueElementProps } from './cell_rendering';
-import { isTab } from '../../../common/components/accessibility/helpers';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { FlyoutHeader, FlyoutHeaderPanel } from '../flyout/header';
-import { TimelineType, TimelineId } from '../../../../common/types/timeline';
+import { TimelineType, TimelineId, RowRenderer } from '../../../../common/types/timeline';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
import { activeTimeline } from '../../containers/active_timeline_context';
import { EVENTS_COUNT_BUTTON_CLASS_NAME, onTimelineTabKeyPressed } from './helpers';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx
index 0f781b0958d02..f4d5570ce40d3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx
@@ -23,6 +23,7 @@ import { useSourcererScope } from '../../../../common/containers/sourcerer';
import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks';
import { PinnedTabContentComponent, Props as PinnedTabContentComponentProps } from '.';
import { Direction } from '../../../../../common/search_strategy';
+import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components';
jest.mock('../../../containers/index', () => ({
useTimelineEvents: jest.fn(),
@@ -57,6 +58,10 @@ jest.mock('../../../../common/lib/kibana', () => {
savedObjects: {
client: {},
},
+ timelines: {
+ getLastUpdated: jest.fn(),
+ getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
+ },
},
}),
useGetUserSavedObjectPermissions: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx
index c01cf5c8aa0f0..b5e3d853bc81c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx
@@ -19,7 +19,6 @@ import { Direction } from '../../../../../common/search_strategy';
import { useTimelineEvents } from '../../../containers/index';
import { defaultHeaders } from '../body/column_headers/default_headers';
import { StatefulBody } from '../body';
-import { RowRenderer } from '../body/renderers/row_renderer';
import { Footer, footerHeight } from '../footer';
import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config';
import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context';
@@ -29,14 +28,18 @@ import { timelineDefaults } from '../../../store/timeline/defaults';
import { useSourcererScope } from '../../../../common/containers/sourcerer';
import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen';
import { TimelineModel } from '../../../store/timeline/model';
-import { ToggleDetailPanel } from '../../../store/timeline/actions';
import { State } from '../../../../common/store';
import { calculateTotalPages } from '../helpers';
-import { TimelineTabs } from '../../../../../common/types/timeline';
+import {
+ ControlColumnProps,
+ RowRenderer,
+ TimelineTabs,
+ ToggleDetailPanel,
+} from '../../../../../common/types/timeline';
import { DetailsPanel } from '../../side_panel';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import { ExitFullScreen } from '../../../../common/components/exit_full_screen';
-import { defaultControlColumn, ControlColumnProps } from '../body/control_columns';
+import { defaultControlColumn } from '../body/control_columns';
const StyledEuiFlyoutBody = styled(EuiFlyoutBody)`
overflow-y: hidden;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx
index 8790d8c98c161..b2b304e16c4a0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx
@@ -22,7 +22,6 @@ import {
SavedQueryTimeFilter,
} from '../../../../../../../../src/plugins/data/public';
import { convertKueryToElasticSearchQuery } from '../../../../common/lib/keury';
-import { KueryFilterQuery, KueryFilterQueryKind } from '../../../../common/store';
import { KqlMode } from '../../../../timelines/store/timeline/model';
import { useSavedQueryServices } from '../../../../common/utils/saved_query_services';
import { DispatchUpdateReduxTime } from '../../../../common/components/super_date_picker';
@@ -30,6 +29,7 @@ import { QueryBar } from '../../../../common/components/query_bar';
import { DataProvider } from '../data_providers/data_provider';
import { buildGlobalQuery } from '../helpers';
import { timelineActions } from '../../../store/timeline';
+import { KueryFilterQuery, KueryFilterQueryKind } from '../../../../../common/types/timeline';
export interface QueryBarTimelineComponentProps {
dataProviders: DataProvider[];
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx
index acae8c8c53cd0..9bf7ee28f3934 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx
@@ -59,6 +59,15 @@ jest.mock('../../../../common/lib/kibana', () => {
savedObjects: {
client: {},
},
+ timelines: {
+ getLastUpdated: jest.fn(),
+ getLoadingPanel: jest.fn(),
+ getUseDraggableKeyboardWrapper: () =>
+ jest.fn().mockReturnValue({
+ onBlur: jest.fn(),
+ onKeyDown: jest.fn(),
+ }),
+ },
},
}),
useGetUserSavedObjectPermissions: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
index 4298f2ff74517..6f0bbd026cd7b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
@@ -17,12 +17,11 @@ import { isEmpty } from 'lodash/fp';
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import styled from 'styled-components';
import { Dispatch } from 'redux';
-import { connect, ConnectedProps } from 'react-redux';
+import { connect, ConnectedProps, useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import { InPortal } from 'react-reverse-portal';
import { timelineActions, timelineSelectors } from '../../../store/timeline';
-import { RowRenderer } from '../body/renderers/row_renderer';
import { CellValueElementProps } from '../cell_rendering';
import { Direction, TimelineItem } from '../../../../../common/search_strategy';
import { useTimelineEvents } from '../../../containers/index';
@@ -34,18 +33,20 @@ import { TimelineHeader } from '../header';
import { calculateTotalPages, combineQueries } from '../helpers';
import { TimelineRefetch } from '../refetch_timeline';
import { esQuery, FilterManager } from '../../../../../../../../src/plugins/data/public';
-import { useManageTimeline } from '../../manage_timeline';
-import { TimelineEventsType, TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
+import {
+ ControlColumnProps,
+ KueryFilterQueryKind,
+ RowRenderer,
+ TimelineEventsType,
+ TimelineId,
+ TimelineTabs,
+ ToggleDetailPanel,
+} from '../../../../../common/types/timeline';
import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config';
import { SuperDatePicker } from '../../../../common/components/super_date_picker';
import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context';
import { PickEventType } from '../search_or_filter/pick_events';
-import {
- inputsModel,
- inputsSelectors,
- KueryFilterQueryKind,
- State,
-} from '../../../../common/store';
+import { inputsModel, inputsSelectors, State } from '../../../../common/store';
import { sourcererActions } from '../../../../common/store/sourcerer';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
import { timelineDefaults } from '../../../../timelines/store/timeline/defaults';
@@ -55,10 +56,9 @@ import { TimelineModel } from '../../../../timelines/store/timeline/model';
import { TimelineDatePickerLock } from '../date_picker_lock';
import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen';
import { activeTimeline } from '../../../containers/active_timeline_context';
-import { ToggleDetailPanel } from '../../../store/timeline/actions';
import { DetailsPanel } from '../../side_panel';
import { ExitFullScreen } from '../../../../common/components/exit_full_screen';
-import { defaultControlColumn, ControlColumnProps } from '../body/control_columns';
+import { defaultControlColumn } from '../body/control_columns';
const TimelineHeaderContainer = styled.div`
margin-top: 6px;
@@ -180,6 +180,7 @@ export const QueryTabContentComponent: React.FC = ({
timerangeKind,
updateEventTypeAndIndexesName,
}) => {
+ const dispatch = useDispatch();
const { portalNode: timelineEventsCountPortalNode } = useTimelineEventsCountPortal();
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
const {
@@ -231,13 +232,14 @@ export const QueryTabContentComponent: React.FC = ({
type: columnType,
}));
- const { initializeTimeline, setIsTimelineLoading } = useManageTimeline();
useEffect(() => {
- initializeTimeline({
- filterManager,
- id: timelineId,
- });
- }, [initializeTimeline, filterManager, timelineId]);
+ dispatch(
+ timelineActions.initializeTGridSettings({
+ filterManager,
+ id: timelineId,
+ })
+ );
+ }, [filterManager, timelineId, dispatch]);
const [
isQueryLoading,
@@ -270,8 +272,13 @@ export const QueryTabContentComponent: React.FC = ({
}, [onEventClosed, timelineId, expandedDetail, showExpandedDetails]);
useEffect(() => {
- setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer });
- }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]);
+ dispatch(
+ timelineActions.updateIsLoading({
+ id: timelineId,
+ isLoading: isQueryLoading || loadingSourcerer,
+ })
+ );
+ }, [loadingSourcerer, timelineId, isQueryLoading, dispatch]);
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
const trailingControlColumns: ControlColumnProps[] = [];
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx
index 4ea4f94abff63..33ab2e0049828 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx
@@ -12,17 +12,13 @@ import { Dispatch } from 'redux';
import deepEqual from 'fast-deep-equal';
import { Filter, FilterManager } from '../../../../../../../../src/plugins/data/public';
-import {
- SerializedFilterQuery,
- State,
- inputsModel,
- inputsSelectors,
-} from '../../../../common/store';
+import { State, inputsModel, inputsSelectors } from '../../../../common/store';
import { timelineActions, timelineSelectors } from '../../../store/timeline';
import { KqlMode, TimelineModel } from '../../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../../timelines/store/timeline/defaults';
import { dispatchUpdateReduxTime } from '../../../../common/components/super_date_picker';
import { SearchOrFilter } from './search_or_filter';
+import { SerializedFilterQuery } from '../../../../../common/types/timeline';
interface OwnProps {
filterManager: FilterManager;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx
index 262709ed98e5a..f1c4b7c3ef089 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx
@@ -10,9 +10,9 @@ import React, { useCallback } from 'react';
import styled, { createGlobalStyle } from 'styled-components';
import { Filter, FilterManager } from '../../../../../../../../src/plugins/data/public';
-import { KueryFilterQuery } from '../../../../common/store';
import { KqlMode } from '../../../../timelines/store/timeline/model';
import { DispatchUpdateReduxTime } from '../../../../common/components/super_date_picker';
+import { KueryFilterQuery } from '../../../../../common/types/timeline';
import { DataProvider } from '../data_providers/data_provider';
import { QueryBarTimeline } from '../query_bar';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
index adaa5f98c88c4..8cdd7722d7fbd 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
@@ -10,7 +10,12 @@ import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 're
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
-import { TimelineTabs, TimelineId, TimelineType } from '../../../../../common/types/timeline';
+import {
+ RowRenderer,
+ TimelineTabs,
+ TimelineId,
+ TimelineType,
+} from '../../../../../common/types/timeline';
import {
useShallowEqualSelector,
useDeepEqualSelector,
@@ -20,7 +25,6 @@ import {
TimelineEventsCountBadge,
} from '../../../../common/hooks/use_timeline_events_count';
import { timelineActions } from '../../../store/timeline';
-import { RowRenderer } from '../body/renderers/row_renderer';
import { CellValueElementProps } from '../cell_rendering';
import {
getActiveTabSelector,
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
index 37fdd5a444b2b..86624ba161a83 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
@@ -69,7 +69,7 @@ export const useTimelineEventsDetails = ({
.search(
request,
{
- strategy: 'securitySolutionTimelineSearchStrategy',
+ strategy: 'timelineSearchStrategy',
abortSignal: abortCtrl.current.signal,
}
)
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
index 17c107899d85a..00df0146e06d5 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
@@ -14,7 +14,7 @@ import { Subscription } from 'rxjs';
import { ESQuery } from '../../../common/typed_json';
import { isCompleteResponse, isErrorResponse } from '../../../../../../src/plugins/data/public';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
-import { inputsModel, KueryFilterQueryKind } from '../../common/store';
+import { inputsModel } from '../../common/store';
import { useKibana } from '../../common/lib/kibana';
import { createFilter } from '../../common/containers/helpers';
import { timelineActions } from '../../timelines/store/timeline';
@@ -33,7 +33,7 @@ import {
} from '../../../common/search_strategy';
import { InspectResponse } from '../../types';
import * as i18n from './translations';
-import { TimelineId } from '../../../common/types/timeline';
+import { KueryFilterQueryKind, TimelineId } from '../../../common/types/timeline';
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
import { activeTimeline } from './active_timeline_context';
import {
@@ -214,9 +214,7 @@ export const useTimelineEvents = ({
searchSubscription$.current = data.search
.search, TimelineResponse>(request, {
strategy:
- request.language === 'eql'
- ? 'securitySolutionTimelineEqlSearchStrategy'
- : 'securitySolutionTimelineSearchStrategy',
+ request.language === 'eql' ? 'timelineEqlSearchStrategy' : 'timelineSearchStrategy',
abortSignal: abortCtrl.current.signal,
})
.subscribe({
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx
index 4a6eab13ba4f1..be93a13ab1c6a 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx
@@ -64,7 +64,7 @@ export const useTimelineKpis = ({
searchSubscription$.current = data.search
.search(request, {
- strategy: 'securitySolutionTimelineSearchStrategy',
+ strategy: 'timelineSearchStrategy',
abortSignal: abortCtrl.current.signal,
})
.subscribe({
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx
index 38eb6d3d222f8..99f45c7d9a4b4 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx
@@ -9,8 +9,8 @@ import { isEmpty } from 'lodash/fp';
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
import { TimelinesStorage } from './types';
import { useKibana } from '../../../common/lib/kibana';
-import { ColumnHeaderOptions, TimelineModel } from '../../store/timeline/model';
-import { TimelineIdLiteral } from '../../../../common/types/timeline';
+import { TimelineModel } from '../../store/timeline/model';
+import { ColumnHeaderOptions, TimelineIdLiteral } from '../../../../common/types/timeline';
export const LOCAL_STORAGE_TIMELINE_KEY = 'timelines';
const EMPTY_TIMELINE = {} as {
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
index 11e9a625d05d0..a3429c9247ffd 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
@@ -8,25 +8,42 @@
import actionCreatorFactory from 'typescript-fsa';
import { Filter } from '../../../../../../../src/plugins/data/public';
-import { Sort } from '../../../timelines/components/timeline/body/sort';
import {
DataProvider,
DataProviderType,
QueryOperator,
} from '../../../timelines/components/timeline/data_providers/data_provider';
-import { SerializedFilterQuery } from '../../../common/store/types';
-import { KqlMode, TimelineModel, ColumnHeaderOptions } from './model';
-import { FieldsEqlOptions, TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
+import { KqlMode, TimelineModel } from './model';
+import { FieldsEqlOptions } from '../../../../common/search_strategy/timeline';
import {
TimelineEventsType,
- TimelineExpandedDetail,
- TimelineExpandedDetailType,
- TimelineTypeLiteral,
RowRendererId,
TimelineTabs,
+ TimelinePersistInput,
+ SerializedFilterQuery,
} from '../../../../common/types/timeline';
import { InsertTimeline } from './types';
+import { tGridActions } from '../../../../../timelines/public';
+export const {
+ applyDeltaToColumnWidth,
+ clearEventsDeleted,
+ clearEventsLoading,
+ clearSelected,
+ initializeTGridSettings,
+ removeColumn,
+ setEventsDeleted,
+ setEventsLoading,
+ setSelected,
+ setTGridSelectAll,
+ toggleDetailPanel,
+ updateColumns,
+ updateIsLoading,
+ updateItemsPerPage,
+ updateItemsPerPageOptions,
+ updateSort,
+ upsertColumn,
+} = tGridActions;
const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline');
@@ -38,62 +55,14 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI
'ADD_NOTE_TO_EVENT'
);
-export type ToggleDetailPanel = TimelineExpandedDetailType & {
- tabType?: TimelineTabs;
- timelineId: string;
-};
-
-export const toggleDetailPanel = actionCreator('TOGGLE_DETAIL_PANEL');
-
-export const upsertColumn = actionCreator<{
- column: ColumnHeaderOptions;
- id: string;
- index: number;
-}>('UPSERT_COLUMN');
-
export const addProvider = actionCreator<{ id: string; provider: DataProvider }>('ADD_PROVIDER');
-export const applyDeltaToColumnWidth = actionCreator<{
- id: string;
- columnId: string;
- delta: number;
-}>('APPLY_DELTA_TO_COLUMN_WIDTH');
-
-export interface TimelineInput {
- id: string;
- dataProviders?: DataProvider[];
- dateRange?: {
- start: string;
- end: string;
- };
- excludedRowRendererIds?: RowRendererId[];
- expandedDetail?: TimelineExpandedDetail;
- filters?: Filter[];
- columns: ColumnHeaderOptions[];
- itemsPerPage?: number;
- indexNames: string[];
- kqlQuery?: {
- filterQuery: SerializedFilterQuery | null;
- };
- show?: boolean;
- sort?: Sort[];
- showCheckboxes?: boolean;
- timelineType?: TimelineTypeLiteral;
- templateTimelineId?: string | null;
- templateTimelineVersion?: number | null;
-}
-
-export const saveTimeline = actionCreator('SAVE_TIMELINE');
+export const saveTimeline = actionCreator('SAVE_TIMELINE');
-export const createTimeline = actionCreator('CREATE_TIMELINE');
+export const createTimeline = actionCreator('CREATE_TIMELINE');
export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT');
-export const removeColumn = actionCreator<{
- id: string;
- columnId: string;
-}>('REMOVE_COLUMN');
-
export const removeProvider = actionCreator<{
id: string;
providerId: string;
@@ -129,16 +98,6 @@ export const endTimelineSaving = actionCreator<{
id: string;
}>('END_TIMELINE_SAVING');
-export const updateIsLoading = actionCreator<{
- id: string;
- isLoading: boolean;
-}>('UPDATE_LOADING');
-
-export const updateColumns = actionCreator<{
- id: string;
- columns: ColumnHeaderOptions[];
-}>('UPDATE_COLUMNS');
-
export const updateDataProviderEnabled = actionCreator<{
id: string;
enabled: boolean;
@@ -189,15 +148,6 @@ export const updateIsFavorite = actionCreator<{ id: string; isFavorite: boolean
export const updateIsLive = actionCreator<{ id: string; isLive: boolean }>('UPDATE_IS_LIVE');
-export const updateItemsPerPage = actionCreator<{ id: string; itemsPerPage: number }>(
- 'UPDATE_ITEMS_PER_PAGE'
-);
-
-export const updateItemsPerPageOptions = actionCreator<{
- id: string;
- itemsPerPageOptions: number[];
-}>('UPDATE_ITEMS_PER_PAGE_OPTIONS');
-
export const updateTitleAndDescription = actionCreator<{
description: string;
id: string;
@@ -216,8 +166,6 @@ export const updateRange = actionCreator<{ id: string; start: string; end: strin
'UPDATE_RANGE'
);
-export const updateSort = actionCreator<{ id: string; sort: Sort[] }>('UPDATE_SORT');
-
export const updateAutoSaveMsg = actionCreator<{
timelineId: string | null;
newTimelineModel: TimelineModel | null;
@@ -235,37 +183,6 @@ export const setFilters = actionCreator<{
filters: Filter[];
}>('SET_TIMELINE_FILTERS');
-export const setSelected = actionCreator<{
- id: string;
- eventIds: Readonly>;
- isSelected: boolean;
- isSelectAllChecked: boolean;
-}>('SET_TIMELINE_SELECTED');
-
-export const clearSelected = actionCreator<{
- id: string;
-}>('CLEAR_TIMELINE_SELECTED');
-
-export const setEventsLoading = actionCreator<{
- id: string;
- eventIds: string[];
- isLoading: boolean;
-}>('SET_TIMELINE_EVENTS_LOADING');
-
-export const clearEventsLoading = actionCreator<{
- id: string;
-}>('CLEAR_TIMELINE_EVENTS_LOADING');
-
-export const setEventsDeleted = actionCreator<{
- id: string;
- eventIds: string[];
- isDeleted: boolean;
-}>('SET_TIMELINE_EVENTS_DELETED');
-
-export const clearEventsDeleted = actionCreator<{
- id: string;
-}>('CLEAR_TIMELINE_EVENTS_DELETED');
-
export const updateEventType = actionCreator<{ id: string; eventType: TimelineEventsType }>(
'UPDATE_EVENT_TYPE'
);
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
index 7e76f6035f8b5..d8fd82005dfbe 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
@@ -10,7 +10,6 @@ import { TimelineType, TimelineStatus, TimelineTabs } from '../../../../common/t
import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers';
import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range';
import { SubsetTimelineModel, TimelineModel } from './model';
-import { Direction } from '../../../../common/search_strategy';
// normalizeTimeRange uses getTimeRangeSettings which cannot be used outside Kibana context if the uiSettings is not false
const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false);
@@ -66,7 +65,7 @@ export const timelineDefaults: SubsetTimelineModel &
{
columnId: '@timestamp',
columnType: 'number',
- sortDirection: Direction.desc,
+ sortDirection: 'desc',
},
],
status: TimelineStatus.draft,
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
index 5f5d76990b5ff..8f2631dac6769 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
@@ -41,6 +41,7 @@ import {
TimelineType,
ResponseTimeline,
TimelineResult,
+ ColumnHeaderOptions,
} from '../../../../common/types/timeline';
import { inputsModel } from '../../../common/store/inputs';
import { addError } from '../../../common/store/app/actions';
@@ -81,7 +82,7 @@ import {
showCallOutUnauthorizedMsg,
saveTimeline,
} from './actions';
-import { ColumnHeaderOptions, TimelineModel } from './model';
+import { TimelineModel } from './model';
import { epicPersistNote, timelineNoteActionsType } from './epic_note';
import { epicPersistPinnedEvent, timelinePinnedEventActionsType } from './epic_pinned_event';
import { epicPersistTimelineFavorite, timelineFavoriteActionsType } from './epic_favorite';
@@ -96,13 +97,11 @@ const timelineActionsType = [
addProvider.type,
addTimeline.type,
dataProviderEdited.type,
- removeColumn.type,
removeProvider.type,
saveTimeline.type,
setExcludedRowRendererIds.type,
setFilters.type,
setSavedQueryId.type,
- updateColumns.type,
updateDataProviderEnabled.type,
updateDataProviderExcluded.type,
updateDataProviderKqlQuery.type,
@@ -110,10 +109,13 @@ const timelineActionsType = [
updateEqlOptions.type,
updateEventType.type,
updateKqlMode.type,
- updateIndexNames.type,
updateProviders.type,
- updateSort.type,
updateTitleAndDescription.type,
+
+ updateIndexNames.type,
+ removeColumn.type,
+ updateColumns.type,
+ updateSort.type,
updateRange.type,
upsertColumn.type,
];
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
index 2172cf8562c97..610c394614c32 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
@@ -8,7 +8,6 @@
import { getOr, omit, uniq, isEmpty, isEqualWith, union } from 'lodash/fp';
import uuid from 'uuid';
-import { ToggleDetailPanel } from './actions';
import { Filter } from '../../../../../../../src/plugins/data/public';
import { Sort } from '../../../timelines/components/timeline/body/sort';
@@ -20,22 +19,24 @@ import {
IS_OPERATOR,
EXISTS_OPERATOR,
} from '../../../timelines/components/timeline/data_providers/data_provider';
-import { SerializedFilterQuery } from '../../../common/store/model';
import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
import {
+ ColumnHeaderOptions,
TimelineEventsType,
- TimelineExpandedDetail,
TimelineTypeLiteral,
TimelineType,
RowRendererId,
TimelineStatus,
TimelineId,
TimelineTabs,
+ SerializedFilterQuery,
+ ToggleDetailPanel,
+ TimelinePersistInput,
} from '../../../../common/types/timeline';
import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range';
import { timelineDefaults } from './defaults';
-import { ColumnHeaderOptions, KqlMode, TimelineModel } from './model';
+import { KqlMode, TimelineModel } from './model';
import { TimelineById } from './types';
import {
DEFAULT_FROM_MOMENT,
@@ -168,47 +169,20 @@ export const addTimelineToStore = ({
};
};
-interface AddNewTimelineParams {
- columns: ColumnHeaderOptions[];
- dataProviders?: DataProvider[];
- dateRange?: {
- start: string;
- end: string;
- };
- excludedRowRendererIds?: RowRendererId[];
- expandedDetail?: TimelineExpandedDetail;
- filters?: Filter[];
- id: string;
- itemsPerPage?: number;
- indexNames: string[];
- kqlQuery?: {
- filterQuery: SerializedFilterQuery | null;
- };
- show?: boolean;
- sort?: Sort[];
- showCheckboxes?: boolean;
+interface AddNewTimelineParams extends TimelinePersistInput {
timelineById: TimelineById;
timelineType: TimelineTypeLiteral;
}
/** Adds a new `Timeline` to the provided collection of `TimelineById` */
export const addNewTimeline = ({
- columns,
- dataProviders = [],
- dateRange: maybeDateRange,
- excludedRowRendererIds = [],
- expandedDetail = {},
- filters = timelineDefaults.filters,
id,
- itemsPerPage = timelineDefaults.itemsPerPage,
- indexNames,
- kqlQuery = { filterQuery: null },
- sort = timelineDefaults.sort,
- show = false,
- showCheckboxes = false,
timelineById,
timelineType,
+ dateRange: maybeDateRange,
+ ...timelineProps
}: AddNewTimelineParams): TimelineById => {
+ const timeline = timelineById[id];
const { from: startDateRange, to: endDateRange } = normalizeTimeRange({ from: '', to: '' });
const dateRange = maybeDateRange ?? { start: startDateRange, end: endDateRange };
const templateTimelineInfo =
@@ -222,23 +196,14 @@ export const addNewTimeline = ({
...timelineById,
[id]: {
id,
+ ...(timeline ? timeline : {}),
...timelineDefaults,
- columns,
- dataProviders,
+ ...timelineProps,
dateRange,
- expandedDetail,
- excludedRowRendererIds,
- filters,
- itemsPerPage,
- indexNames,
- kqlQuery,
- sort,
- show,
savedObjectId: null,
version: null,
isSaving: false,
isLoading: false,
- showCheckboxes,
timelineType,
...templateTimelineInfo,
},
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
index 559cec57dd55c..a68617536c6af 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
@@ -5,63 +5,29 @@
* 2.0.
*/
-import { EuiDataGridColumn } from '@elastic/eui';
-
-import { Filter, IFieldSubType } from '../../../../../../../src/plugins/data/public';
-
import { DataProvider } from '../../components/timeline/data_providers/data_provider';
-import { Sort } from '../../components/timeline/body/sort';
-import {
- EqlOptionsSelected,
- TimelineNonEcsData,
-} from '../../../../common/search_strategy/timeline';
-import { SerializedFilterQuery } from '../../../common/store/types';
+import { EqlOptionsSelected } from '../../../../common/search_strategy/timeline';
import type {
TimelineEventsType,
- TimelineExpandedDetail,
TimelineType,
TimelineStatus,
- RowRendererId,
TimelineTabs,
} from '../../../../common/types/timeline';
import { PinnedEvent } from '../../../../common/types/timeline/pinned_event';
+import type { TGridModelForTimeline } from '../../../../../timelines/public';
export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages
export type KqlMode = 'filter' | 'search';
export type ColumnHeaderType = 'not-filtered' | 'text-filter';
-/** Uniquely identifies a column */
-export type ColumnId = string;
-
-/** The specification of a column header */
-export type ColumnHeaderOptions = Pick<
- EuiDataGridColumn,
- 'display' | 'displayAsText' | 'id' | 'initialWidth'
-> & {
- aggregatable?: boolean;
- category?: string;
- columnHeaderType: ColumnHeaderType;
- description?: string;
- example?: string;
- format?: string;
- linkField?: string;
- placeholder?: string;
- subType?: IFieldSubType;
- type?: string;
-};
-
-export interface TimelineModel {
+export type TimelineModel = TGridModelForTimeline & {
/** The selected tab to displayed in the timeline */
activeTab: TimelineTabs;
prevActiveTab: TimelineTabs;
- /** The columns displayed in the timeline */
- columns: ColumnHeaderOptions[];
/** Timeline saved object owner */
createdBy?: string;
/** The sources of the event data shown in the timeline */
dataProviders: DataProvider[];
- /** Events to not be rendered **/
- deletedEventIds: string[];
/** A summary of the events and notes in this timeline */
description: string;
eqlOptions: EqlOptionsSelected;
@@ -69,40 +35,16 @@ export interface TimelineModel {
eventType?: TimelineEventsType;
/** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */
eventIdToNoteIds: Record;
- /** A list of Ids of excluded Row Renderers */
- excludedRowRendererIds: RowRendererId[];
- /** This holds the view information for the flyout when viewing timeline in a consuming view (i.e. hosts page) or the side panel in the primary timeline view */
- expandedDetail: TimelineExpandedDetail;
- filters?: Filter[];
- /** When non-empty, display a graph view for this event */
- graphEventId?: string;
/** The chronological history of actions related to this timeline */
historyIds: string[];
/** The chronological history of actions related to this timeline */
highlightedDropAndProviderId: string;
- /** Uniquely identifies the timeline */
- id: string;
- /** TO DO sourcerer @X define this */
- indexNames: string[];
- /** If selectAll checkbox in header is checked **/
- isSelectAllChecked: boolean;
- /** Events to be rendered as loading **/
- loadingEventIds: string[];
- savedObjectId: string | null;
/** When true, this timeline was marked as "favorite" by the user */
isFavorite: boolean;
/** When true, the timeline will update as new data arrives */
isLive: boolean;
- /** The number of items to show in a single page of results */
- itemsPerPage: number;
- /** Displays a series of choices that when selected, become the value of `itemsPerPage` */
- itemsPerPageOptions: number[];
/** determines the behavior of the KQL bar */
kqlMode: KqlMode;
- /** the KQL query in the KQL bar */
- kqlQuery: {
- filterQuery: SerializedFilterQuery | null;
- };
/** Title */
title: string;
/** timelineType: default | template */
@@ -116,30 +58,18 @@ export interface TimelineModel {
/** Events pinned to this timeline */
pinnedEventIds: Record;
pinnedEventsSaveObject: Record;
- /** Specifies the granularity of the date range (e.g. 1 Day / Week / Month) applicable to the mini-map */
- dateRange: {
- start: string;
- end: string;
- };
showSaveModal?: boolean;
savedQueryId?: string | null;
- /** Events selected on this timeline -- eventId to TimelineNonEcsData[] mapping of data required for batch actions **/
- selectedEventIds: Record;
/** When true, show the timeline flyover */
show: boolean;
- /** When true, shows checkboxes enabling selection. Selected events store in selectedEventIds **/
- showCheckboxes: boolean;
- /** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */
- sort: Sort[];
/** status: active | draft */
status: TimelineStatus;
/** updated saved object timestamp */
updated?: number;
/** timeline is saving */
isSaving: boolean;
- isLoading: boolean;
version: string | null;
-}
+};
export type SubsetTimelineModel = Readonly<
Pick<
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
index 1c65c01a0bdfc..8a5c8546d3834 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
@@ -7,6 +7,7 @@
import { cloneDeep } from 'lodash/fp';
import {
+ ColumnHeaderOptions,
TimelineType,
TimelineStatus,
TimelineTabs,
@@ -47,7 +48,7 @@ import {
upsertTimelineColumn,
updateGraphEventId,
} from './helpers';
-import { ColumnHeaderOptions, TimelineModel } from './model';
+import { TimelineModel } from './model';
import { timelineDefaults } from './defaults';
import { TimelineById } from './types';
import { Direction } from '../../../../common/search_strategy';
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
index 80c6d83075719..656784c330e45 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
@@ -13,32 +13,22 @@ import {
addNoteToEvent,
addProvider,
addTimeline,
- applyDeltaToColumnWidth,
applyKqlFilterQuery,
- clearEventsDeleted,
- clearEventsLoading,
- clearSelected,
createTimeline,
dataProviderEdited,
endTimelineSaving,
pinEvent,
- removeColumn,
removeProvider,
- setEventsDeleted,
setActiveTabTimeline,
- setEventsLoading,
setExcludedRowRendererIds,
setFilters,
setInsertTimeline,
setSavedQueryId,
- setSelected,
showCallOutUnauthorizedMsg,
showTimeline,
startTimelineSaving,
- toggleDetailPanel,
unPinEvent,
updateAutoSaveMsg,
- updateColumns,
updateDataProviderEnabled,
updateDataProviderExcluded,
updateDataProviderKqlQuery,
@@ -47,18 +37,13 @@ import {
updateIndexNames,
updateIsFavorite,
updateIsLive,
- updateIsLoading,
- updateItemsPerPage,
- updateItemsPerPageOptions,
updateKqlMode,
updatePageIndex,
updateProviders,
updateRange,
- updateSort,
updateTimeline,
updateTimelineGraphEventId,
updateTitleAndDescription,
- upsertColumn,
toggleModalSaveTimeline,
updateEqlOptions,
} from './actions';
@@ -69,23 +54,15 @@ import {
addTimelineNoteToEvent,
addTimelineProvider,
addTimelineToStore,
- applyDeltaToTimelineColumnWidth,
applyKqlFilterQueryDraft,
pinTimelineEvent,
- removeTimelineColumn,
removeTimelineProvider,
- setDeletedTimelineEvents,
- setLoadingTimelineEvents,
- setSelectedTimelineEvents,
unPinTimelineEvent,
updateExcludedRowRenderersIds,
- updateTimelineColumns,
updateTimelineIsFavorite,
updateTimelineIsLive,
- updateTimelineItemsPerPage,
updateTimelineKqlMode,
updateTimelinePageIndex,
- updateTimelinePerPageOptions,
updateTimelineProviderEnabled,
updateTimelineProviderExcluded,
updateTimelineProviderProperties,
@@ -94,13 +71,10 @@ import {
updateTimelineProviders,
updateTimelineRange,
updateTimelineShowTimeline,
- updateTimelineSort,
updateTimelineTitleAndDescription,
- upsertTimelineColumn,
updateSavedQuery,
updateGraphEventId,
updateFilters,
- updateTimelineDetailsPanel,
updateTimelineEventType,
} from './helpers';
@@ -123,53 +97,17 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
...state,
timelineById: addTimelineToStore({ id, timeline, timelineById: state.timelineById }),
}))
- .case(
- createTimeline,
- (
- state,
- {
+ .case(createTimeline, (state, { id, timelineType = TimelineType.default, ...timelineProps }) => {
+ return {
+ ...state,
+ timelineById: addNewTimeline({
id,
- dataProviders,
- dateRange,
- excludedRowRendererIds,
- expandedDetail = {},
- show,
- columns,
- itemsPerPage,
- indexNames,
- kqlQuery,
- sort,
- showCheckboxes,
- timelineType = TimelineType.default,
- filters,
- }
- ) => {
- return {
- ...state,
- timelineById: addNewTimeline({
- columns,
- dataProviders,
- dateRange,
- excludedRowRendererIds,
- expandedDetail,
- filters,
- id,
- itemsPerPage,
- indexNames,
- kqlQuery,
- sort,
- show,
- showCheckboxes,
- timelineById: state.timelineById,
- timelineType,
- }),
- };
- }
- )
- .case(upsertColumn, (state, { column, id, index }) => ({
- ...state,
- timelineById: upsertTimelineColumn({ column, id, index, timelineById: state.timelineById }),
- }))
+ timelineById: state.timelineById,
+ timelineType,
+ ...timelineProps,
+ }),
+ };
+ })
.case(addHistory, (state, { id, historyId }) => ({
...state,
timelineById: addTimelineHistory({ id, historyId, timelineById: state.timelineById }),
@@ -182,19 +120,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
...state,
timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }),
}))
- .case(toggleDetailPanel, (state, action) => ({
- ...state,
- timelineById: {
- ...state.timelineById,
- [action.timelineId]: {
- ...state.timelineById[action.timelineId],
- expandedDetail: {
- ...state.timelineById[action.timelineId].expandedDetail,
- ...updateTimelineDetailsPanel(action),
- },
- },
- },
- }))
.case(addProvider, (state, { id, provider }) => ({
...state,
timelineById: addTimelineProvider({ id, provider, timelineById: state.timelineById }),
@@ -215,27 +140,10 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
...state,
timelineById: updateGraphEventId({ id, graphEventId, timelineById: state.timelineById }),
}))
- .case(applyDeltaToColumnWidth, (state, { id, columnId, delta }) => ({
- ...state,
- timelineById: applyDeltaToTimelineColumnWidth({
- id,
- columnId,
- delta,
- timelineById: state.timelineById,
- }),
- }))
.case(pinEvent, (state, { id, eventId }) => ({
...state,
timelineById: pinTimelineEvent({ id, eventId, timelineById: state.timelineById }),
}))
- .case(removeColumn, (state, { id, columnId }) => ({
- ...state,
- timelineById: removeTimelineColumn({
- id,
- columnId,
- timelineById: state.timelineById,
- }),
- }))
.case(removeProvider, (state, { id, providerId, andProviderId }) => ({
...state,
timelineById: removeTimelineProvider({
@@ -265,44 +173,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
},
},
}))
- .case(setEventsDeleted, (state, { id, eventIds, isDeleted }) => ({
- ...state,
- timelineById: setDeletedTimelineEvents({
- id,
- eventIds,
- timelineById: state.timelineById,
- isDeleted,
- }),
- }))
- .case(clearEventsDeleted, (state, { id }) => ({
- ...state,
- timelineById: {
- ...state.timelineById,
- [id]: {
- ...state.timelineById[id],
- deletedEventIds: [],
- },
- },
- }))
- .case(setEventsLoading, (state, { id, eventIds, isLoading }) => ({
- ...state,
- timelineById: setLoadingTimelineEvents({
- id,
- eventIds,
- timelineById: state.timelineById,
- isLoading,
- }),
- }))
- .case(clearEventsLoading, (state, { id }) => ({
- ...state,
- timelineById: {
- ...state.timelineById,
- [id]: {
- ...state.timelineById[id],
- loadingEventIds: [],
- },
- },
- }))
.case(setExcludedRowRendererIds, (state, { id, excludedRowRendererIds }) => ({
...state,
timelineById: updateExcludedRowRenderersIds({
@@ -311,37 +181,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
timelineById: state.timelineById,
}),
}))
- .case(setSelected, (state, { id, eventIds, isSelected, isSelectAllChecked }) => ({
- ...state,
- timelineById: setSelectedTimelineEvents({
- id,
- eventIds,
- timelineById: state.timelineById,
- isSelected,
- isSelectAllChecked,
- }),
- }))
- .case(clearSelected, (state, { id }) => ({
- ...state,
- timelineById: {
- ...state.timelineById,
- [id]: {
- ...state.timelineById[id],
- selectedEventIds: {},
- isSelectAllChecked: false,
- },
- },
- }))
- .case(updateIsLoading, (state, { id, isLoading }) => ({
- ...state,
- timelineById: {
- ...state.timelineById,
- [id]: {
- ...state.timelineById[id],
- isLoading,
- },
- },
- }))
.case(updateTimeline, (state, { id, timeline }) => ({
...state,
timelineById: {
@@ -353,14 +192,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
...state,
timelineById: unPinTimelineEvent({ id, eventId, timelineById: state.timelineById }),
}))
- .case(updateColumns, (state, { id, columns }) => ({
- ...state,
- timelineById: updateTimelineColumns({
- id,
- columns,
- timelineById: state.timelineById,
- }),
- }))
.case(updateEventType, (state, { id, eventType }) => ({
...state,
timelineById: updateTimelineEventType({ id, eventType, timelineById: state.timelineById }),
@@ -394,10 +225,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
...state,
timelineById: updateTimelineRange({ id, start, end, timelineById: state.timelineById }),
}))
- .case(updateSort, (state, { id, sort }) => ({
- ...state,
- timelineById: updateTimelineSort({ id, sort, timelineById: state.timelineById }),
- }))
.case(updateDataProviderEnabled, (state, { id, enabled, providerId, andProviderId }) => ({
...state,
timelineById: updateTimelineProviderEnabled({
@@ -454,14 +281,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
timelineById: state.timelineById,
}),
}))
- .case(updateItemsPerPage, (state, { id, itemsPerPage }) => ({
- ...state,
- timelineById: updateTimelineItemsPerPage({
- id,
- itemsPerPage,
- timelineById: state.timelineById,
- }),
- }))
.case(updatePageIndex, (state, { id, activePage }) => ({
...state,
timelineById: updateTimelinePageIndex({
@@ -470,14 +289,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
timelineById: state.timelineById,
}),
}))
- .case(updateItemsPerPageOptions, (state, { id, itemsPerPageOptions }) => ({
- ...state,
- timelineById: updateTimelinePerPageOptions({
- id,
- itemsPerPageOptions,
- timelineById: state.timelineById,
- }),
- }))
.case(updateAutoSaveMsg, (state, { timelineId, newTimelineModel }) => ({
...state,
autoSavedWarningMsg: {
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts
index b05e6568be6c3..f46b55bcd3345 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts
@@ -7,11 +7,14 @@
import { createSelector } from 'reselect';
+import { tGridSelectors } from '../../../../../timelines/public';
import { State } from '../../../common/store/types';
import { TimelineModel } from './model';
import { AutoSavedWarningMsg, InsertTimeline, TimelineById } from './types';
+export const { getManageTimelineById } = tGridSelectors;
+
const selectTimelineById = (state: State): TimelineById => state.timeline.timelineById;
const selectAutoSaveMsg = (state: State): AutoSavedWarningMsg => state.timeline.autoSavedWarningMsg;
diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts
index d4e2601554187..aad685f9fb103 100644
--- a/x-pack/plugins/security_solution/public/types.ts
+++ b/x-pack/plugins/security_solution/public/types.ts
@@ -23,6 +23,7 @@ import {
} from '../../triggers_actions_ui/public';
import { CasesUiStart } from '../../cases/public';
import { SecurityPluginSetup } from '../../security/public';
+import { TimelinesUIStart } from '../../timelines/public';
import { ResolverPluginSetup } from './resolver/types';
import { Inspect } from '../common/search_strategy';
import { MlPluginSetup, MlPluginStart } from '../../ml/public';
@@ -56,6 +57,7 @@ export interface StartPlugins {
licensing: LicensingPluginStart;
newsfeed?: NewsfeedPublicPluginStart;
triggersActionsUi: TriggersActionsStart;
+ timelines: TimelinesUIStart;
uiActions: UiActionsStart;
ml?: MlPluginStart;
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts
new file mode 100644
index 0000000000000..76389d7376fc8
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts
@@ -0,0 +1,791 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EqlSearchStrategyResponse } from '../../../../../../../../src/plugins/data/common';
+import { EqlSearchResponse } from '../../../../../common/detection_engine/types';
+
+export const sequenceResponse = ({
+ rawResponse: {
+ body: {
+ is_partial: false,
+ is_running: false,
+ took: 527,
+ timed_out: false,
+ hits: {
+ total: {
+ value: 10,
+ relation: 'eq',
+ },
+ sequences: [
+ {
+ join_keys: ['win2019-endpoint-mr-pedro'],
+ events: [
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'qhymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTIzODAtMTMyNTUwNzg2ODkuOTY1Nzg1NTAw',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU2OC0xMzI1NTA3ODY2Ny4zMjk3MDY2MDA=',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ name: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTUyODQtMTMyNTcyOTQ2MjMuOTk2NTkxMDAw',
+ executable: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3377092Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293866,
+ ingested: '2021-02-08T21:57:26.417559711Z',
+ created: '2021-02-08T21:50:28.3377092Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'log_on',
+ id: 'LzzWB9jjGmCwGMvk++++FG/O',
+ category: ['authentication', 'session'],
+ type: ['start'],
+ dataset: 'endpoint.events.security',
+ outcome: 'success',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'qxymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU4MC0xMzI1NTA3ODY2Ny45MTg5Njc1MDA=',
+ executable: 'C:\\Windows\\System32\\lsass.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3377142Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293867,
+ ingested: '2021-02-08T21:57:26.417596906Z',
+ created: '2021-02-08T21:50:28.3377142Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'log_on',
+ id: 'LzzWB9jjGmCwGMvk++++FG/P',
+ category: ['authentication', 'session'],
+ type: ['start'],
+ dataset: 'endpoint.events.security',
+ outcome: 'success',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'rBymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU4MC0xMzI1NTA3ODY2Ny45MTg5Njc1MDA=',
+ executable: 'C:\\Windows\\System32\\lsass.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3381013Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293868,
+ ingested: '2021-02-08T21:57:26.417632166Z',
+ created: '2021-02-08T21:50:28.3381013Z',
+ kind: 'event',
+ module: 'endpoint',
+ id: 'LzzWB9jjGmCwGMvk++++FG/Q',
+ category: [],
+ type: [],
+ dataset: 'endpoint.events.security',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ ],
+ },
+ {
+ join_keys: ['win2019-endpoint-mr-pedro'],
+ events: [
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'qxymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU4MC0xMzI1NTA3ODY2Ny45MTg5Njc1MDA=',
+ executable: 'C:\\Windows\\System32\\lsass.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3377142Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293867,
+ ingested: '2021-02-08T21:57:26.417596906Z',
+ created: '2021-02-08T21:50:28.3377142Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'log_on',
+ id: 'LzzWB9jjGmCwGMvk++++FG/P',
+ category: ['authentication', 'session'],
+ type: ['start'],
+ dataset: 'endpoint.events.security',
+ outcome: 'success',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'rBymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU4MC0xMzI1NTA3ODY2Ny45MTg5Njc1MDA=',
+ executable: 'C:\\Windows\\System32\\lsass.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3381013Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293868,
+ ingested: '2021-02-08T21:57:26.417632166Z',
+ created: '2021-02-08T21:50:28.3381013Z',
+ kind: 'event',
+ module: 'endpoint',
+ id: 'LzzWB9jjGmCwGMvk++++FG/Q',
+ category: [],
+ type: [],
+ dataset: 'endpoint.events.security',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.process-default-2021.02.02-000005',
+ _id: 'pxymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTUyODQtMTMyNTcyOTQ2MjMuOTk2NTkxMDAw',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTIzODAtMTMyNTUwNzg2ODkuOTY1Nzg1NTAw',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU2OC0xMzI1NTA3ODY2Ny4zMjk3MDY2MDA=',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ code_signature: [
+ {
+ trusted: true,
+ subject_name: 'Microsoft Corporation',
+ exists: true,
+ status: 'trusted',
+ },
+ ],
+ token: {
+ integrity_level_name: 'high',
+ elevation_level: 'default',
+ },
+ },
+ args: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe', '-y'],
+ parent: {
+ args: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe', '-R'],
+ name: 'sshd.exe',
+ pid: 5284,
+ args_count: 2,
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTUyODQtMTMyNTcyOTQ2MjMuOTk2NTkxMDAw',
+ command_line: '"C:\\Program Files\\OpenSSH-Win64\\sshd.exe" -R',
+ executable: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ },
+ code_signature: {
+ trusted: true,
+ subject_name: 'Microsoft Corporation',
+ exists: true,
+ status: 'trusted',
+ },
+ name: 'sshd.exe',
+ pid: 6368,
+ args_count: 2,
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTYzNjgtMTMyNTcyOTQ2MjguMzQ0NjM1NTAw',
+ command_line: '"C:\\Program Files\\OpenSSH-Win64\\sshd.exe" -y',
+ executable: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ hash: {
+ sha1: '631244d731f406394c17c7dfd85203e317c74814',
+ sha256: 'e6a972f9db27de18be225095b3b3141b945be8aadc4014c8704ae5acafe3e8e0',
+ md5: '331ba0e529810ef718dd3efbd1242302',
+ },
+ },
+ message: 'Endpoint process event',
+ '@timestamp': '2021-02-08T21:50:28.3446355Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.process',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293863,
+ ingested: '2021-02-08T21:57:26.417387865Z',
+ created: '2021-02-08T21:50:28.3446355Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'start',
+ id: 'LzzWB9jjGmCwGMvk++++FG/K',
+ category: ['process'],
+ type: ['start'],
+ dataset: 'endpoint.events.process',
+ },
+ user: {
+ domain: '',
+ name: '',
+ },
+ },
+ },
+ ],
+ },
+ {
+ join_keys: ['win2019-endpoint-mr-pedro'],
+ events: [
+ {
+ _index: '.ds-logs-endpoint.events.security-default-2021.02.05-000005',
+ _id: 'rBymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU4MC0xMzI1NTA3ODY2Ny45MTg5Njc1MDA=',
+ executable: 'C:\\Windows\\System32\\lsass.exe',
+ },
+ message: 'Endpoint security event',
+ '@timestamp': '2021-02-08T21:50:28.3381013Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.security',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293868,
+ ingested: '2021-02-08T21:57:26.417632166Z',
+ created: '2021-02-08T21:50:28.3381013Z',
+ kind: 'event',
+ module: 'endpoint',
+ id: 'LzzWB9jjGmCwGMvk++++FG/Q',
+ category: [],
+ type: [],
+ dataset: 'endpoint.events.security',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'SYSTEM',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.process-default-2021.02.02-000005',
+ _id: 'pxymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTUyODQtMTMyNTcyOTQ2MjMuOTk2NTkxMDAw',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTIzODAtMTMyNTUwNzg2ODkuOTY1Nzg1NTAw',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU2OC0xMzI1NTA3ODY2Ny4zMjk3MDY2MDA=',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ code_signature: [
+ {
+ trusted: true,
+ subject_name: 'Microsoft Corporation',
+ exists: true,
+ status: 'trusted',
+ },
+ ],
+ token: {
+ integrity_level_name: 'high',
+ elevation_level: 'default',
+ },
+ },
+ args: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe', '-y'],
+ parent: {
+ args: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe', '-R'],
+ name: 'sshd.exe',
+ pid: 5284,
+ args_count: 2,
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTUyODQtMTMyNTcyOTQ2MjMuOTk2NTkxMDAw',
+ command_line: '"C:\\Program Files\\OpenSSH-Win64\\sshd.exe" -R',
+ executable: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ },
+ code_signature: {
+ trusted: true,
+ subject_name: 'Microsoft Corporation',
+ exists: true,
+ status: 'trusted',
+ },
+ name: 'sshd.exe',
+ pid: 6368,
+ args_count: 2,
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTYzNjgtMTMyNTcyOTQ2MjguMzQ0NjM1NTAw',
+ command_line: '"C:\\Program Files\\OpenSSH-Win64\\sshd.exe" -y',
+ executable: 'C:\\Program Files\\OpenSSH-Win64\\sshd.exe',
+ hash: {
+ sha1: '631244d731f406394c17c7dfd85203e317c74814',
+ sha256: 'e6a972f9db27de18be225095b3b3141b945be8aadc4014c8704ae5acafe3e8e0',
+ md5: '331ba0e529810ef718dd3efbd1242302',
+ },
+ },
+ message: 'Endpoint process event',
+ '@timestamp': '2021-02-08T21:50:28.3446355Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.process',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293863,
+ ingested: '2021-02-08T21:57:26.417387865Z',
+ created: '2021-02-08T21:50:28.3446355Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'start',
+ id: 'LzzWB9jjGmCwGMvk++++FG/K',
+ category: ['process'],
+ type: ['start'],
+ dataset: 'endpoint.events.process',
+ },
+ user: {
+ domain: '',
+ name: '',
+ },
+ },
+ },
+ {
+ _index: '.ds-logs-endpoint.events.network-default-2021.02.02-000005',
+ _id: 'qBymg3cBX5UUcOOYP3Ec',
+ _source: {
+ agent: {
+ id: '1d15cf9e-3dc7-5b97-f586-743f7c2518b2',
+ type: 'endpoint',
+ version: '7.10.0',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTU2OC0xMzI1NTA3ODY2Ny4zMjk3MDY2MDA=',
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTQ2OC0xMzI1NTA3ODY2NS42Mzg5MzY1MDA=',
+ ],
+ },
+ name: 'svchost.exe',
+ pid: 968,
+ entity_id:
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTk2OC0xMzI1NTA3ODY3My4yNjQyNDcyMDA=',
+ executable: 'C:\\Windows\\System32\\svchost.exe',
+ },
+ destination: {
+ address: '10.128.0.57',
+ port: 3389,
+ bytes: 1681,
+ ip: '10.128.0.57',
+ },
+ source: {
+ address: '142.202.189.139',
+ port: 16151,
+ bytes: 1224,
+ ip: '142.202.189.139',
+ },
+ message: 'Endpoint network event',
+ network: {
+ transport: 'tcp',
+ type: 'ipv4',
+ direction: 'incoming',
+ },
+ '@timestamp': '2021-02-08T21:50:28.5553532Z',
+ ecs: {
+ version: '1.5.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.network',
+ },
+ elastic: {
+ agent: {
+ id: 'f5dec71e-438c-424e-ac9b-0281f10412b9',
+ },
+ },
+ host: {
+ hostname: 'win2019-endpoint-mr-pedro',
+ os: {
+ Ext: {
+ variant: 'Windows Server 2019 Datacenter',
+ },
+ kernel: '1809 (10.0.17763.1697)',
+ name: 'Windows',
+ family: 'windows',
+ version: '1809 (10.0.17763.1697)',
+ platform: 'windows',
+ full: 'Windows Server 2019 Datacenter 1809 (10.0.17763.1697)',
+ },
+ ip: ['10.128.0.57', 'fe80::9ced:8f1c:880b:3e1f', '127.0.0.1', '::1'],
+ name: 'win2019-endpoint-mr-pedro',
+ id: 'd8ad572e-d224-4044-a57d-f5a84c0dfe5d',
+ mac: ['42:01:0a:80:00:39'],
+ architecture: 'x86_64',
+ },
+ event: {
+ sequence: 3293864,
+ ingested: '2021-02-08T21:57:26.417451347Z',
+ created: '2021-02-08T21:50:28.5553532Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'disconnect_received',
+ id: 'LzzWB9jjGmCwGMvk++++FG/L',
+ category: ['network'],
+ type: ['end'],
+ dataset: 'endpoint.events.network',
+ },
+ user: {
+ domain: 'NT AUTHORITY',
+ name: 'NETWORK SERVICE',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ statusCode: 200,
+ headers: {},
+ meta: {},
+ hits: {},
+ },
+} as unknown) as EqlSearchStrategyResponse>;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts
index 6529c594dd5a5..da5c89a3102a1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts
@@ -7,10 +7,8 @@
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
-
-import { sequenceResponse } from '../../../search_strategy/timeline/eql/__mocks__';
-
import { createEqlAlertType } from './eql';
+import { sequenceResponse } from './__mocks__/eql';
import { createRuleTypeMocks } from './__mocks__/rule_type';
describe('EQL alerts', () => {
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts
index 94e70e4eb001b..3a37a49d03dcd 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts
@@ -7,13 +7,20 @@
import { AuthenticatedUser } from '../../../../../../security/common/model';
-import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline';
+import { TimelineStatus, TimelineType, SavedTimeline } from '../../../../../common/types/timeline';
+import { NoteSavedObject } from '../../../../../common/types/timeline/note';
import { pickSavedTimeline } from './pick_saved_timeline';
describe('pickSavedTimeline', () => {
const mockDateNow = new Date('2020-04-03T23:00:00.000Z').valueOf();
- const getMockSavedTimeline = () => ({
+ const getMockSavedTimeline = (): SavedTimeline & {
+ savedObjectId?: string | null;
+ version?: string;
+ eventNotes?: NoteSavedObject[];
+ globalNotes?: NoteSavedObject[];
+ pinnedEventIds?: [];
+ } => ({
savedObjectId: '7af80430-03f4-11eb-9d9d-ffba20fabba8',
version: 'WzQ0ODgsMV0=',
created: 1601563413330,
@@ -91,7 +98,7 @@ describe('pickSavedTimeline', () => {
test('Updating a timeline', () => {
const savedTimeline = getMockSavedTimeline();
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -113,7 +120,7 @@ describe('pickSavedTimeline', () => {
test('Updating a timeline', () => {
const savedTimeline = getMockSavedTimeline();
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -143,7 +150,7 @@ describe('pickSavedTimeline', () => {
test('Updating a timeline with a new title', () => {
const savedTimeline = getMockSavedTimeline();
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -152,7 +159,7 @@ describe('pickSavedTimeline', () => {
test('Updating a timeline without title', () => {
const savedTimeline = getMockSavedTimeline();
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -161,7 +168,7 @@ describe('pickSavedTimeline', () => {
test('Updating an immutable timeline with a new title', () => {
const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.immutable };
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -192,7 +199,7 @@ describe('pickSavedTimeline', () => {
test('Updating an untitled draft timeline with a title', () => {
const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft };
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -201,7 +208,7 @@ describe('pickSavedTimeline', () => {
test('Updating a draft timeline with a new title', () => {
const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft };
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
@@ -210,7 +217,7 @@ describe('pickSavedTimeline', () => {
test('Updating a draft timeline without title', () => {
const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft };
- const timelineId = savedTimeline.savedObjectId;
+ const timelineId = savedTimeline.savedObjectId ?? null;
const userInfo = { username: 'elastic' } as AuthenticatedUser;
const result = pickSavedTimeline(timelineId, savedTimeline, userInfo);
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts
index a28084cd78154..3e00a33966f17 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts
@@ -12,10 +12,9 @@ import { SavedTimeline, TimelineType, TimelineStatus } from '../../../../../comm
export const pickSavedTimeline = (
timelineId: string | null,
- savedTimeline: SavedTimeline,
+ savedTimeline: SavedTimeline & { savedObjectId?: string | null },
userInfo: AuthenticatedUser | null
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
-): any => {
+): SavedTimeline & { savedObjectId?: string | null } => {
const dateNow = new Date().valueOf();
if (timelineId == null) {
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index ac9d854f18211..4bcbcb71d048c 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -83,8 +83,6 @@ import { initUsageCollectors } from './usage';
import type { SecuritySolutionRequestHandlerContext } from './types';
import { registerTrustedAppsRoutes } from './endpoint/routes/trusted_apps';
import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution';
-import { securitySolutionIndexFieldsProvider } from './search_strategy/index_fields';
-import { securitySolutionTimelineSearchStrategyProvider } from './search_strategy/timeline';
import { TelemetryEventsSender } from './lib/telemetry/sender';
import {
TelemetryPluginStart,
@@ -92,7 +90,6 @@ import {
} from '../../../../src/plugins/telemetry/server';
import { licenseService } from './lib/license';
import { PolicyWatcher } from './endpoint/lib/policy/license_watch';
-import { securitySolutionTimelineEqlSearchStrategyProvider } from './search_strategy/timeline/eql';
import { parseExperimentalConfigValue } from '../common/experimental_features';
import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet';
@@ -451,30 +448,10 @@ export class Plugin implements IPlugin {
- describe('#formatTimelineData', () => {
- it('happy path', async () => {
- const res = await formatTimelineData(
- [
- '@timestamp',
- 'host.name',
- 'destination.ip',
- 'source.ip',
- 'source.geo.location',
- 'threat.indicator.matched.field',
- ],
- TIMELINE_EVENTS_FIELDS,
- eventHit
- );
- expect(res).toEqual({
- cursor: {
- tiebreaker: 'beats-ci-immutable-ubuntu-1804-1605624279743236239',
- value: '1605624488922',
- },
- node: {
- _id: 'tkCt1nUBaEgqnrVSZ8R_',
- _index: 'auditbeat-7.8.0-2020.11.05-000003',
- data: [
- {
- field: '@timestamp',
- value: ['2020-11-17T14:48:08.922Z'],
- },
- {
- field: 'host.name',
- value: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
- },
- {
- field: 'threat.indicator.matched.field',
- value: ['matched_field', 'other_matched_field', 'matched_field_2'],
- },
- {
- field: 'source.geo.location',
- value: [`{"lon":118.7778,"lat":32.0617}`],
- },
- ],
- ecs: {
- '@timestamp': ['2020-11-17T14:48:08.922Z'],
- _id: 'tkCt1nUBaEgqnrVSZ8R_',
- _index: 'auditbeat-7.8.0-2020.11.05-000003',
- agent: {
- type: ['auditbeat'],
- },
- event: {
- action: ['process_started'],
- category: ['process'],
- dataset: ['process'],
- kind: ['event'],
- module: ['system'],
- type: ['start'],
- },
- host: {
- id: ['e59991e835905c65ed3e455b33e13bd6'],
- ip: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
- name: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
- os: {
- family: ['debian'],
- },
- },
- message: ['Process go (PID: 4313) by user jenkins STARTED'],
- process: {
- args: ['go', 'vet', './...'],
- entity_id: ['Z59cIkAAIw8ZoK0H'],
- executable: [
- '/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
- ],
- hash: {
- sha1: ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
- },
- name: ['go'],
- pid: ['4313'],
- ppid: ['3977'],
- working_directory: [
- '/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
- ],
- },
- timestamp: '2020-11-17T14:48:08.922Z',
- user: {
- name: ['jenkins'],
- },
- threat: {
- indicator: [
- {
- event: {
- dataset: [],
- reference: [],
- },
- matched: {
- atomic: ['matched_atomic'],
- field: ['matched_field', 'other_matched_field'],
- type: [],
- },
- provider: ['yourself'],
- },
- {
- event: {
- dataset: [],
- reference: [],
- },
- matched: {
- atomic: ['matched_atomic_2'],
- field: ['matched_field_2'],
- type: [],
- },
- provider: ['other_you'],
- },
- ],
- },
- },
- },
- });
- });
-
- it('rule signal results', async () => {
- const response: EventHit = {
- _index: '.siem-signals-patrykkopycinski-default-000007',
- _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562',
- _score: 0,
- _source: {
- signal: {
- threshold_result: {
- count: 10000,
- value: '2a990c11-f61b-4c8e-b210-da2574e9f9db',
- },
- parent: {
- depth: 0,
- index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- id: '0268af90-d8da-576a-9747-2a191519416a',
- type: 'event',
- },
- depth: 1,
- _meta: {
- version: 14,
- },
- rule: {
- note: null,
- throttle: null,
- references: [],
- severity_mapping: [],
- description: 'asdasd',
- created_at: '2021-01-09T11:25:45.046Z',
- language: 'kuery',
- threshold: {
- field: '',
- value: 200,
- },
- building_block_type: null,
- output_index: '.siem-signals-patrykkopycinski-default',
- type: 'threshold',
- rule_name_override: null,
- enabled: true,
- exceptions_list: [],
- updated_at: '2021-01-09T13:36:39.204Z',
- timestamp_override: null,
- from: 'now-360s',
- id: '696c24e0-526d-11eb-836c-e1620268b945',
- timeline_id: null,
- max_signals: 100,
- severity: 'low',
- risk_score: 21,
- risk_score_mapping: [],
- author: [],
- query: '_id :*',
- index: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- filters: [
- {
- $state: {
- store: 'appState',
- },
- meta: {
- negate: false,
- alias: null,
- disabled: false,
- type: 'exists',
- value: 'exists',
- key: '_index',
- },
- exists: {
- field: '_index',
- },
- },
- {
- $state: {
- store: 'appState',
- },
- meta: {
- negate: false,
- alias: 'id_exists',
- disabled: false,
- type: 'exists',
- value: 'exists',
- key: '_id',
- },
- exists: {
- field: '_id',
- },
- },
- ],
- created_by: 'patryk_test_user',
- version: 1,
- saved_id: null,
- tags: [],
- rule_id: '2a990c11-f61b-4c8e-b210-da2574e9f9db',
- license: '',
- immutable: false,
- timeline_title: null,
- meta: {
- from: '1m',
- kibana_siem_app_url: 'http://localhost:5601/app/security',
- },
- name: 'Threshold test',
- updated_by: 'patryk_test_user',
- interval: '5m',
- false_positives: [],
- to: 'now',
- threat: [],
- actions: [],
- },
- original_time: '2021-01-09T13:39:32.595Z',
- ancestors: [
- {
- depth: 0,
- index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- id: '0268af90-d8da-576a-9747-2a191519416a',
- type: 'event',
- },
- ],
- parents: [
- {
- depth: 0,
- index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- id: '0268af90-d8da-576a-9747-2a191519416a',
- type: 'event',
- },
- ],
- status: 'open',
- },
- },
- fields: {
- 'signal.rule.output_index': ['.siem-signals-patrykkopycinski-default'],
- 'signal.rule.from': ['now-360s'],
- 'signal.rule.language': ['kuery'],
- '@timestamp': ['2021-01-09T13:41:40.517Z'],
- 'signal.rule.query': ['_id :*'],
- 'signal.rule.type': ['threshold'],
- 'signal.rule.id': ['696c24e0-526d-11eb-836c-e1620268b945'],
- 'signal.rule.risk_score': [21],
- 'signal.status': ['open'],
- 'event.kind': ['signal'],
- 'signal.original_time': ['2021-01-09T13:39:32.595Z'],
- 'signal.rule.severity': ['low'],
- 'signal.rule.version': ['1'],
- 'signal.rule.index': [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- 'signal.rule.name': ['Threshold test'],
- 'signal.rule.to': ['now'],
- },
- _type: '',
- sort: ['1610199700517'],
- aggregations: {},
- };
-
- expect(
- await formatTimelineData(
- ['@timestamp', 'host.name', 'destination.ip', 'source.ip'],
- TIMELINE_EVENTS_FIELDS,
- response
- )
- ).toEqual({
- cursor: {
- tiebreaker: null,
- value: '',
- },
- node: {
- _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562',
- _index: '.siem-signals-patrykkopycinski-default-000007',
- data: [
- {
- field: '@timestamp',
- value: ['2021-01-09T13:41:40.517Z'],
- },
- ],
- ecs: {
- '@timestamp': ['2021-01-09T13:41:40.517Z'],
- timestamp: '2021-01-09T13:41:40.517Z',
- _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562',
- _index: '.siem-signals-patrykkopycinski-default-000007',
- event: {
- kind: ['signal'],
- },
- signal: {
- original_time: ['2021-01-09T13:39:32.595Z'],
- status: ['open'],
- threshold_result: ['{"count":10000,"value":"2a990c11-f61b-4c8e-b210-da2574e9f9db"}'],
- rule: {
- building_block_type: [],
- exceptions_list: [],
- from: ['now-360s'],
- id: ['696c24e0-526d-11eb-836c-e1620268b945'],
- index: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- language: ['kuery'],
- name: ['Threshold test'],
- output_index: ['.siem-signals-patrykkopycinski-default'],
- risk_score: ['21'],
- query: ['_id :*'],
- severity: ['low'],
- to: ['now'],
- type: ['threshold'],
- version: ['1'],
- timeline_id: [],
- timeline_title: [],
- saved_id: [],
- note: [],
- threshold: [
- JSON.stringify({
- field: '',
- value: 200,
- }),
- ],
- filters: [
- JSON.stringify({
- $state: {
- store: 'appState',
- },
- meta: {
- negate: false,
- alias: null,
- disabled: false,
- type: 'exists',
- value: 'exists',
- key: '_index',
- },
- exists: {
- field: '_index',
- },
- }),
- JSON.stringify({
- $state: {
- store: 'appState',
- },
- meta: {
- negate: false,
- alias: 'id_exists',
- disabled: false,
- type: 'exists',
- value: 'exists',
- key: '_id',
- },
- exists: {
- field: '_id',
- },
- }),
- ],
- },
- },
- },
- },
- });
- });
- });
-
- describe('#buildObjectForFieldPath', () => {
- it('builds an object from a single non-nested field', () => {
- expect(buildObjectForFieldPath('@timestamp', eventHit)).toEqual({
- '@timestamp': ['2020-11-17T14:48:08.922Z'],
- });
- });
-
- it('builds an object with no fields response', () => {
- const { fields, ...fieldLessHit } = eventHit;
- // @ts-expect-error fieldLessHit is intentionally missing fields
- expect(buildObjectForFieldPath('@timestamp', fieldLessHit)).toEqual({
- '@timestamp': [],
- });
- });
-
- it('does not misinterpret non-nested fields with a common prefix', () => {
- // @ts-expect-error hit is minimal
- const hit: EventHit = {
- fields: {
- 'foo.bar': ['baz'],
- 'foo.barBaz': ['foo'],
- },
- };
-
- expect(buildObjectForFieldPath('foo.barBaz', hit)).toEqual({
- foo: { barBaz: ['foo'] },
- });
- });
-
- it('builds an array of objects from a nested field', () => {
- // @ts-expect-error hit is minimal
- const hit: EventHit = {
- fields: {
- foo: [{ bar: ['baz'] }],
- },
- };
- expect(buildObjectForFieldPath('foo.bar', hit)).toEqual({
- foo: [{ bar: ['baz'] }],
- });
- });
-
- it('builds intermediate objects for nested fields', () => {
- // @ts-expect-error nestedHit is minimal
- const nestedHit: EventHit = {
- fields: {
- 'foo.bar': [
- {
- baz: ['host.name'],
- },
- ],
- },
- };
- expect(buildObjectForFieldPath('foo.bar.baz', nestedHit)).toEqual({
- foo: {
- bar: [
- {
- baz: ['host.name'],
- },
- ],
- },
- });
- });
-
- it('builds intermediate objects at multiple levels', () => {
- expect(buildObjectForFieldPath('threat.indicator.matched.atomic', eventHit)).toEqual({
- threat: {
- indicator: [
- {
- matched: {
- atomic: ['matched_atomic'],
- },
- },
- {
- matched: {
- atomic: ['matched_atomic_2'],
- },
- },
- ],
- },
- });
- });
-
- it('preserves multiple values for a single leaf', () => {
- expect(buildObjectForFieldPath('threat.indicator.matched.field', eventHit)).toEqual({
- threat: {
- indicator: [
- {
- matched: {
- field: ['matched_field', 'other_matched_field'],
- },
- },
- {
- matched: {
- field: ['matched_field_2'],
- },
- },
- ],
- },
- });
- });
-
- describe('multiple levels of nested fields', () => {
- let nestedHit: EventHit;
-
- beforeEach(() => {
- // @ts-expect-error nestedHit is minimal
- nestedHit = {
- fields: {
- 'nested_1.foo': [
- {
- 'nested_2.bar': [
- { leaf: ['leaf_value'], leaf_2: ['leaf_2_value'] },
- { leaf_2: ['leaf_2_value_2', 'leaf_2_value_3'] },
- ],
- },
- {
- 'nested_2.bar': [
- { leaf: ['leaf_value_2'], leaf_2: ['leaf_2_value_4'] },
- { leaf: ['leaf_value_3'], leaf_2: ['leaf_2_value_5'] },
- ],
- },
- ],
- },
- };
- });
-
- it('includes objects without the field', () => {
- expect(buildObjectForFieldPath('nested_1.foo.nested_2.bar.leaf', nestedHit)).toEqual({
- nested_1: {
- foo: [
- {
- nested_2: {
- bar: [{ leaf: ['leaf_value'] }, { leaf: [] }],
- },
- },
- {
- nested_2: {
- bar: [{ leaf: ['leaf_value_2'] }, { leaf: ['leaf_value_3'] }],
- },
- },
- ],
- },
- });
- });
-
- it('groups multiple leaf values', () => {
- expect(buildObjectForFieldPath('nested_1.foo.nested_2.bar.leaf_2', nestedHit)).toEqual({
- nested_1: {
- foo: [
- {
- nested_2: {
- bar: [
- { leaf_2: ['leaf_2_value'] },
- { leaf_2: ['leaf_2_value_2', 'leaf_2_value_3'] },
- ],
- },
- },
- {
- nested_2: {
- bar: [{ leaf_2: ['leaf_2_value_4'] }, { leaf_2: ['leaf_2_value_5'] }],
- },
- },
- ],
- },
- });
- });
- });
- });
-
- describe('#buildFieldsRequest', () => {
- it('happy path', async () => {
- const res = await buildFieldsRequest([
- '@timestamp',
- 'host.name',
- 'destination.ip',
- 'source.ip',
- 'source.geo.location',
- 'threat.indicator.matched.field',
- ]);
- expect(res).toEqual([
- {
- field: '@timestamp',
- include_unmapped: true,
- },
- {
- field: 'host.name',
- include_unmapped: true,
- },
- {
- field: 'destination.ip',
- include_unmapped: true,
- },
- {
- field: 'source.ip',
- include_unmapped: true,
- },
- {
- field: 'source.geo.location',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.matched.field',
- include_unmapped: true,
- },
- {
- field: 'signal.status',
- include_unmapped: true,
- },
- {
- field: 'signal.group.id',
- include_unmapped: true,
- },
- {
- field: 'signal.original_time',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.filters',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.from',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.language',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.query',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.name',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.to',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.id',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.index',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.type',
- include_unmapped: true,
- },
- {
- field: 'signal.original_event.kind',
- include_unmapped: true,
- },
- {
- field: 'signal.original_event.module',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.version',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.severity',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.risk_score',
- include_unmapped: true,
- },
- {
- field: 'signal.threshold_result',
- include_unmapped: true,
- },
- {
- field: 'event.code',
- include_unmapped: true,
- },
- {
- field: 'event.module',
- include_unmapped: true,
- },
- {
- field: 'event.action',
- include_unmapped: true,
- },
- {
- field: 'event.category',
- include_unmapped: true,
- },
- {
- field: 'user.name',
- include_unmapped: true,
- },
- {
- field: 'message',
- include_unmapped: true,
- },
- {
- field: 'system.auth.ssh.signature',
- include_unmapped: true,
- },
- {
- field: 'system.auth.ssh.method',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.arch',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.entity_id',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.name',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.size',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.summary',
- include_unmapped: true,
- },
- {
- field: 'system.audit.package.version',
- include_unmapped: true,
- },
- {
- field: 'event.created',
- include_unmapped: true,
- },
- {
- field: 'event.dataset',
- include_unmapped: true,
- },
- {
- field: 'event.duration',
- include_unmapped: true,
- },
- {
- field: 'event.end',
- include_unmapped: true,
- },
- {
- field: 'event.hash',
- include_unmapped: true,
- },
- {
- field: 'event.id',
- include_unmapped: true,
- },
- {
- field: 'event.kind',
- include_unmapped: true,
- },
- {
- field: 'event.original',
- include_unmapped: true,
- },
- {
- field: 'event.outcome',
- include_unmapped: true,
- },
- {
- field: 'event.risk_score',
- include_unmapped: true,
- },
- {
- field: 'event.risk_score_norm',
- include_unmapped: true,
- },
- {
- field: 'event.severity',
- include_unmapped: true,
- },
- {
- field: 'event.start',
- include_unmapped: true,
- },
- {
- field: 'event.timezone',
- include_unmapped: true,
- },
- {
- field: 'event.type',
- include_unmapped: true,
- },
- {
- field: 'agent.type',
- include_unmapped: true,
- },
- {
- field: 'auditd.result',
- include_unmapped: true,
- },
- {
- field: 'auditd.session',
- include_unmapped: true,
- },
- {
- field: 'auditd.data.acct',
- include_unmapped: true,
- },
- {
- field: 'auditd.data.terminal',
- include_unmapped: true,
- },
- {
- field: 'auditd.data.op',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.actor.primary',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.actor.secondary',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.object.primary',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.object.secondary',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.object.type',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.how',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.message_type',
- include_unmapped: true,
- },
- {
- field: 'auditd.summary.sequence',
- include_unmapped: true,
- },
- {
- field: 'file.Ext.original.path',
- include_unmapped: true,
- },
- {
- field: 'file.name',
- include_unmapped: true,
- },
- {
- field: 'file.target_path',
- include_unmapped: true,
- },
- {
- field: 'file.extension',
- include_unmapped: true,
- },
- {
- field: 'file.type',
- include_unmapped: true,
- },
- {
- field: 'file.device',
- include_unmapped: true,
- },
- {
- field: 'file.inode',
- include_unmapped: true,
- },
- {
- field: 'file.uid',
- include_unmapped: true,
- },
- {
- field: 'file.owner',
- include_unmapped: true,
- },
- {
- field: 'file.gid',
- include_unmapped: true,
- },
- {
- field: 'file.group',
- include_unmapped: true,
- },
- {
- field: 'file.mode',
- include_unmapped: true,
- },
- {
- field: 'file.size',
- include_unmapped: true,
- },
- {
- field: 'file.mtime',
- include_unmapped: true,
- },
- {
- field: 'file.ctime',
- include_unmapped: true,
- },
- {
- field: 'file.path',
- include_unmapped: true,
- },
- {
- field: 'file.Ext.code_signature',
- include_unmapped: true,
- },
- {
- field: 'file.Ext.code_signature.subject_name',
- include_unmapped: true,
- },
- {
- field: 'file.Ext.code_signature.trusted',
- include_unmapped: true,
- },
- {
- field: 'file.hash.sha256',
- include_unmapped: true,
- },
- {
- field: 'host.os.family',
- include_unmapped: true,
- },
- {
- field: 'host.id',
- include_unmapped: true,
- },
- {
- field: 'host.ip',
- include_unmapped: true,
- },
- {
- field: 'registry.key',
- include_unmapped: true,
- },
- {
- field: 'registry.path',
- include_unmapped: true,
- },
- {
- field: 'rule.reference',
- include_unmapped: true,
- },
- {
- field: 'source.bytes',
- include_unmapped: true,
- },
- {
- field: 'source.packets',
- include_unmapped: true,
- },
- {
- field: 'source.port',
- include_unmapped: true,
- },
- {
- field: 'source.geo.continent_name',
- include_unmapped: true,
- },
- {
- field: 'source.geo.country_name',
- include_unmapped: true,
- },
- {
- field: 'source.geo.country_iso_code',
- include_unmapped: true,
- },
- {
- field: 'source.geo.city_name',
- include_unmapped: true,
- },
- {
- field: 'source.geo.region_iso_code',
- include_unmapped: true,
- },
- {
- field: 'source.geo.region_name',
- include_unmapped: true,
- },
- {
- field: 'destination.bytes',
- include_unmapped: true,
- },
- {
- field: 'destination.packets',
- include_unmapped: true,
- },
- {
- field: 'destination.port',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.continent_name',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.country_name',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.country_iso_code',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.city_name',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.region_iso_code',
- include_unmapped: true,
- },
- {
- field: 'destination.geo.region_name',
- include_unmapped: true,
- },
- {
- field: 'dns.question.name',
- include_unmapped: true,
- },
- {
- field: 'dns.question.type',
- include_unmapped: true,
- },
- {
- field: 'dns.resolved_ip',
- include_unmapped: true,
- },
- {
- field: 'dns.response_code',
- include_unmapped: true,
- },
- {
- field: 'endgame.exit_code',
- include_unmapped: true,
- },
- {
- field: 'endgame.file_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.file_path',
- include_unmapped: true,
- },
- {
- field: 'endgame.logon_type',
- include_unmapped: true,
- },
- {
- field: 'endgame.parent_process_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.pid',
- include_unmapped: true,
- },
- {
- field: 'endgame.process_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.subject_domain_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.subject_logon_id',
- include_unmapped: true,
- },
- {
- field: 'endgame.subject_user_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.target_domain_name',
- include_unmapped: true,
- },
- {
- field: 'endgame.target_logon_id',
- include_unmapped: true,
- },
- {
- field: 'endgame.target_user_name',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.saved_id',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.timeline_id',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.timeline_title',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.output_index',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.note',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.threshold',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.exceptions_list',
- include_unmapped: true,
- },
- {
- field: 'signal.rule.building_block_type',
- include_unmapped: true,
- },
- {
- field: 'suricata.eve.proto',
- include_unmapped: true,
- },
- {
- field: 'suricata.eve.flow_id',
- include_unmapped: true,
- },
- {
- field: 'suricata.eve.alert.signature',
- include_unmapped: true,
- },
- {
- field: 'suricata.eve.alert.signature_id',
- include_unmapped: true,
- },
- {
- field: 'network.bytes',
- include_unmapped: true,
- },
- {
- field: 'network.community_id',
- include_unmapped: true,
- },
- {
- field: 'network.direction',
- include_unmapped: true,
- },
- {
- field: 'network.packets',
- include_unmapped: true,
- },
- {
- field: 'network.protocol',
- include_unmapped: true,
- },
- {
- field: 'network.transport',
- include_unmapped: true,
- },
- {
- field: 'http.version',
- include_unmapped: true,
- },
- {
- field: 'http.request.method',
- include_unmapped: true,
- },
- {
- field: 'http.request.body.bytes',
- include_unmapped: true,
- },
- {
- field: 'http.request.body.content',
- include_unmapped: true,
- },
- {
- field: 'http.request.referrer',
- include_unmapped: true,
- },
- {
- field: 'http.response.status_code',
- include_unmapped: true,
- },
- {
- field: 'http.response.body.bytes',
- include_unmapped: true,
- },
- {
- field: 'http.response.body.content',
- include_unmapped: true,
- },
- {
- field: 'tls.client_certificate.fingerprint.sha1',
- include_unmapped: true,
- },
- {
- field: 'tls.fingerprints.ja3.hash',
- include_unmapped: true,
- },
- {
- field: 'tls.server_certificate.fingerprint.sha1',
- include_unmapped: true,
- },
- {
- field: 'user.domain',
- include_unmapped: true,
- },
- {
- field: 'winlog.event_id',
- include_unmapped: true,
- },
- {
- field: 'process.exit_code',
- include_unmapped: true,
- },
- {
- field: 'process.hash.md5',
- include_unmapped: true,
- },
- {
- field: 'process.hash.sha1',
- include_unmapped: true,
- },
- {
- field: 'process.hash.sha256',
- include_unmapped: true,
- },
- {
- field: 'process.parent.name',
- include_unmapped: true,
- },
- {
- field: 'process.parent.pid',
- include_unmapped: true,
- },
- {
- field: 'process.pid',
- include_unmapped: true,
- },
- {
- field: 'process.name',
- include_unmapped: true,
- },
- {
- field: 'process.ppid',
- include_unmapped: true,
- },
- {
- field: 'process.args',
- include_unmapped: true,
- },
- {
- field: 'process.entity_id',
- include_unmapped: true,
- },
- {
- field: 'process.executable',
- include_unmapped: true,
- },
- {
- field: 'process.title',
- include_unmapped: true,
- },
- {
- field: 'process.working_directory',
- include_unmapped: true,
- },
- {
- field: 'zeek.session_id',
- include_unmapped: true,
- },
- {
- field: 'zeek.connection.local_resp',
- include_unmapped: true,
- },
- {
- field: 'zeek.connection.local_orig',
- include_unmapped: true,
- },
- {
- field: 'zeek.connection.missed_bytes',
- include_unmapped: true,
- },
- {
- field: 'zeek.connection.state',
- include_unmapped: true,
- },
- {
- field: 'zeek.connection.history',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.suppress_for',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.msg',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.note',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.sub',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.dst',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.dropped',
- include_unmapped: true,
- },
- {
- field: 'zeek.notice.peer_descr',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.AA',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.qclass_name',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.RD',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.qtype_name',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.qtype',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.query',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.trans_id',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.qclass',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.RA',
- include_unmapped: true,
- },
- {
- field: 'zeek.dns.TC',
- include_unmapped: true,
- },
- {
- field: 'zeek.http.resp_mime_types',
- include_unmapped: true,
- },
- {
- field: 'zeek.http.trans_depth',
- include_unmapped: true,
- },
- {
- field: 'zeek.http.status_msg',
- include_unmapped: true,
- },
- {
- field: 'zeek.http.resp_fuids',
- include_unmapped: true,
- },
- {
- field: 'zeek.http.tags',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.session_ids',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.timedout',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.local_orig',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.tx_host',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.source',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.is_orig',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.overflow_bytes',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.sha1',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.duration',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.depth',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.analyzers',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.mime_type',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.rx_host',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.total_bytes',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.fuid',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.seen_bytes',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.missing_bytes',
- include_unmapped: true,
- },
- {
- field: 'zeek.files.md5',
- include_unmapped: true,
- },
- {
- field: 'zeek.ssl.cipher',
- include_unmapped: true,
- },
- {
- field: 'zeek.ssl.established',
- include_unmapped: true,
- },
- {
- field: 'zeek.ssl.resumed',
- include_unmapped: true,
- },
- {
- field: 'zeek.ssl.version',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.matched.atomic',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.matched.type',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.event.dataset',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.event.reference',
- include_unmapped: true,
- },
- {
- field: 'threat.indicator.provider',
- include_unmapped: true,
- },
- ]);
- });
-
- it('remove internal attributes starting with _', async () => {
- const res = await buildFieldsRequest([
- '@timestamp',
- '_id',
- 'host.name',
- 'destination.ip',
- 'source.ip',
- 'source.geo.location',
- '_type',
- 'threat.indicator.matched.field',
- ]);
- expect(res.some((f) => f.field === '_id')).toEqual(false);
- expect(res.some((f) => f.field === '_type')).toEqual(false);
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json
index bebfd9ca88c23..0df41b9f988b7 100644
--- a/x-pack/plugins/security_solution/tsconfig.json
+++ b/x-pack/plugins/security_solution/tsconfig.json
@@ -42,5 +42,6 @@
{ "path": "../ml/tsconfig.json" },
{ "path": "../spaces/tsconfig.json" },
{ "path": "../security/tsconfig.json"},
+ { "path": "../timelines/tsconfig.json"},
]
}
diff --git a/x-pack/plugins/timelines/README.md b/x-pack/plugins/timelines/README.md
index 441a505903698..0c14953837d02 100644
--- a/x-pack/plugins/timelines/README.md
+++ b/x-pack/plugins/timelines/README.md
@@ -3,9 +3,9 @@ Timelines is a plugin that provides a grid component with accompanying server si
## Using timelines in another plugin
-- Add `TimelinesPluginSetup` to Kibana plugin `SetupServices` dependencies:
+- Add `TimelinesPluginUI` to Kibana plugin `SetupServices` dependencies:
```ts
-timelines: TimelinesPluginSetup;
+timelines: TimelinesPluginUI;
```
- Once `timelines` is added as a required plugin in the consuming plugin's kibana.json, timeline functionality will be available as any other kibana plugin, ie PluginSetupDependencies.timelines.getTimeline()
diff --git a/x-pack/plugins/timelines/common/constants.ts b/x-pack/plugins/timelines/common/constants.ts
new file mode 100644
index 0000000000000..86ff9d501f148
--- /dev/null
+++ b/x-pack/plugins/timelines/common/constants.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
diff --git a/x-pack/plugins/timelines/common/ecs/agent/index.ts b/x-pack/plugins/timelines/common/ecs/agent/index.ts
new file mode 100644
index 0000000000000..2332b60f1a3ca
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/agent/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface AgentEcs {
+ type?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/auditd/index.ts b/x-pack/plugins/timelines/common/ecs/auditd/index.ts
new file mode 100644
index 0000000000000..f210f8862dc44
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/auditd/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface AuditdEcs {
+ result?: string[];
+
+ session?: string[];
+
+ data?: AuditdDataEcs;
+
+ summary?: SummaryEcs;
+
+ sequence?: string[];
+}
+
+export interface AuditdDataEcs {
+ acct?: string[];
+
+ terminal?: string[];
+
+ op?: string[];
+}
+
+export interface SummaryEcs {
+ actor?: PrimarySecondaryEcs;
+
+ object?: PrimarySecondaryEcs;
+
+ how?: string[];
+
+ message_type?: string[];
+
+ sequence?: string[];
+}
+
+export interface PrimarySecondaryEcs {
+ primary?: string[];
+
+ secondary?: string[];
+
+ type?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/cloud/index.ts b/x-pack/plugins/timelines/common/ecs/cloud/index.ts
new file mode 100644
index 0000000000000..a169e5561c6b6
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/cloud/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface CloudEcs {
+ instance?: CloudInstanceEcs;
+ machine?: CloudMachineEcs;
+ provider?: string[];
+ region?: string[];
+}
+
+export interface CloudMachineEcs {
+ type?: string[];
+}
+
+export interface CloudInstanceEcs {
+ id?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/destination/index.ts b/x-pack/plugins/timelines/common/ecs/destination/index.ts
new file mode 100644
index 0000000000000..2d3b6154276b9
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/destination/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { GeoEcs } from '../geo';
+
+export interface DestinationEcs {
+ bytes?: number[];
+
+ ip?: string[];
+
+ port?: number[];
+
+ domain?: string[];
+
+ geo?: GeoEcs;
+
+ packets?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/dns/index.ts b/x-pack/plugins/timelines/common/ecs/dns/index.ts
new file mode 100644
index 0000000000000..e0f142d9cf57a
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/dns/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface DnsEcs {
+ question?: DnsQuestionEcs;
+
+ resolved_ip?: string[];
+
+ response_code?: string[];
+}
+
+export interface DnsQuestionEcs {
+ name?: string[];
+
+ type?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.test.ts b/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.test.ts
new file mode 100644
index 0000000000000..e27b15f021257
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.test.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { extendMap } from './extend_map';
+
+describe('ecs_fields test', () => {
+ describe('extendMap', () => {
+ test('it should extend a record', () => {
+ const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+ };
+ const expected: Record = {
+ 'host.os.family': 'host.os.family',
+ 'host.os.full': 'host.os.full',
+ 'host.os.kernel': 'host.os.kernel',
+ 'host.os.platform': 'host.os.platform',
+ 'host.os.version': 'host.os.version',
+ };
+ expect(extendMap('host', osFieldsMap)).toEqual(expected);
+ });
+
+ test('it should extend a sample hosts record', () => {
+ const hostMap: Record = {
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.name': 'host.name',
+ };
+ const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+ };
+ const expected: Record = {
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.name': 'host.name',
+ 'host.os.family': 'host.os.family',
+ 'host.os.full': 'host.os.full',
+ 'host.os.kernel': 'host.os.kernel',
+ 'host.os.platform': 'host.os.platform',
+ 'host.os.version': 'host.os.version',
+ };
+ const output = { ...hostMap, ...extendMap('host', osFieldsMap) };
+ expect(output).toEqual(expected);
+ });
+ });
+});
diff --git a/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.ts b/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.ts
new file mode 100644
index 0000000000000..184e6b4f32566
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/ecs_fields/extend_map.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const extendMap = (
+ path: string,
+ map: Readonly>
+): Readonly> =>
+ Object.entries(map).reduce>((accum, [key, value]) => {
+ accum[`${path}.${key}`] = `${path}.${value}`;
+ return accum;
+ }, {});
diff --git a/x-pack/plugins/timelines/common/ecs/ecs_fields/index.ts b/x-pack/plugins/timelines/common/ecs/ecs_fields/index.ts
new file mode 100644
index 0000000000000..292822019fc9c
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/ecs_fields/index.ts
@@ -0,0 +1,359 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { extendMap } from './extend_map';
+
+export const auditdMap: Readonly> = {
+ 'auditd.result': 'auditd.result',
+ 'auditd.session': 'auditd.session',
+ 'auditd.data.acct': 'auditd.data.acct',
+ 'auditd.data.terminal': 'auditd.data.terminal',
+ 'auditd.data.op': 'auditd.data.op',
+ 'auditd.summary.actor.primary': 'auditd.summary.actor.primary',
+ 'auditd.summary.actor.secondary': 'auditd.summary.actor.secondary',
+ 'auditd.summary.object.primary': 'auditd.summary.object.primary',
+ 'auditd.summary.object.secondary': 'auditd.summary.object.secondary',
+ 'auditd.summary.object.type': 'auditd.summary.object.type',
+ 'auditd.summary.how': 'auditd.summary.how',
+ 'auditd.summary.message_type': 'auditd.summary.message_type',
+ 'auditd.summary.sequence': 'auditd.summary.sequence',
+};
+
+export const cloudFieldsMap: Readonly> = {
+ 'cloud.account.id': 'cloud.account.id',
+ 'cloud.availability_zone': 'cloud.availability_zone',
+ 'cloud.instance.id': 'cloud.instance.id',
+ 'cloud.instance.name': 'cloud.instance.name',
+ 'cloud.machine.type': 'cloud.machine.type',
+ 'cloud.provider': 'cloud.provider',
+ 'cloud.region': 'cloud.region',
+};
+
+export const fileMap: Readonly> = {
+ 'file.name': 'file.name',
+ 'file.path': 'file.path',
+ 'file.target_path': 'file.target_path',
+ 'file.extension': 'file.extension',
+ 'file.type': 'file.type',
+ 'file.device': 'file.device',
+ 'file.inode': 'file.inode',
+ 'file.uid': 'file.uid',
+ 'file.owner': 'file.owner',
+ 'file.gid': 'file.gid',
+ 'file.group': 'file.group',
+ 'file.mode': 'file.mode',
+ 'file.size': 'file.size',
+ 'file.mtime': 'file.mtime',
+ 'file.ctime': 'file.ctime',
+};
+
+export const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.name': 'os.name',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+};
+
+export const hostFieldsMap: Readonly> = {
+ 'host.architecture': 'host.architecture',
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.mac': 'host.mac',
+ 'host.name': 'host.name',
+ ...extendMap('host', osFieldsMap),
+};
+
+export const processFieldsMap: Readonly> = {
+ 'process.hash.md5': 'process.hash.md5',
+ 'process.hash.sha1': 'process.hash.sha1',
+ 'process.hash.sha256': 'process.hash.sha256',
+ 'process.pid': 'process.pid',
+ 'process.name': 'process.name',
+ 'process.ppid': 'process.ppid',
+ 'process.args': 'process.args',
+ 'process.entity_id': 'process.entity_id',
+ 'process.executable': 'process.executable',
+ 'process.title': 'process.title',
+ 'process.thread': 'process.thread',
+ 'process.working_directory': 'process.working_directory',
+};
+
+export const agentFieldsMap: Readonly> = {
+ 'agent.type': 'agent.type',
+};
+
+export const userFieldsMap: Readonly> = {
+ 'user.domain': 'user.domain',
+ 'user.id': 'user.id',
+ 'user.name': 'user.name',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.full_name': 'user.full_name',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.email': 'user.email',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.hash': 'user.hash',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.group': 'user.group',
+};
+
+export const winlogFieldsMap: Readonly> = {
+ 'winlog.event_id': 'winlog.event_id',
+};
+
+export const suricataFieldsMap: Readonly> = {
+ 'suricata.eve.flow_id': 'suricata.eve.flow_id',
+ 'suricata.eve.proto': 'suricata.eve.proto',
+ 'suricata.eve.alert.signature': 'suricata.eve.alert.signature',
+ 'suricata.eve.alert.signature_id': 'suricata.eve.alert.signature_id',
+};
+
+export const tlsFieldsMap: Readonly> = {
+ 'tls.client_certificate.fingerprint.sha1': 'tls.client_certificate.fingerprint.sha1',
+ 'tls.fingerprints.ja3.hash': 'tls.fingerprints.ja3.hash',
+ 'tls.server_certificate.fingerprint.sha1': 'tls.server_certificate.fingerprint.sha1',
+};
+
+export const urlFieldsMap: Readonly> = {
+ 'url.original': 'url.original',
+ 'url.domain': 'url.domain',
+ 'user.username': 'user.username',
+ 'user.password': 'user.password',
+};
+
+export const httpFieldsMap: Readonly> = {
+ 'http.version': 'http.version',
+ 'http.request': 'http.request',
+ 'http.request.method': 'http.request.method',
+ 'http.request.body.bytes': 'http.request.body.bytes',
+ 'http.request.body.content': 'http.request.body.content',
+ 'http.request.referrer': 'http.request.referrer',
+ 'http.response.status_code': 'http.response.status_code',
+ 'http.response.body': 'http.response.body',
+ 'http.response.body.bytes': 'http.response.body.bytes',
+ 'http.response.body.content': 'http.response.body.content',
+};
+
+export const zeekFieldsMap: Readonly> = {
+ 'zeek.session_id': 'zeek.session_id',
+ 'zeek.connection.local_resp': 'zeek.connection.local_resp',
+ 'zeek.connection.local_orig': 'zeek.connection.local_orig',
+ 'zeek.connection.missed_bytes': 'zeek.connection.missed_bytes',
+ 'zeek.connection.state': 'zeek.connection.state',
+ 'zeek.connection.history': 'zeek.connection.history',
+ 'zeek.notice.suppress_for': 'zeek.notice.suppress_for',
+ 'zeek.notice.msg': 'zeek.notice.msg',
+ 'zeek.notice.note': 'zeek.notice.note',
+ 'zeek.notice.sub': 'zeek.notice.sub',
+ 'zeek.notice.dst': 'zeek.notice.dst',
+ 'zeek.notice.dropped': 'zeek.notice.dropped',
+ 'zeek.notice.peer_descr': 'zeek.notice.peer_descr',
+ 'zeek.dns.AA': 'zeek.dns.AA',
+ 'zeek.dns.qclass_name': 'zeek.dns.qclass_name',
+ 'zeek.dns.RD': 'zeek.dns.RD',
+ 'zeek.dns.qtype_name': 'zeek.dns.qtype_name',
+ 'zeek.dns.qtype': 'zeek.dns.qtype',
+ 'zeek.dns.query': 'zeek.dns.query',
+ 'zeek.dns.trans_id': 'zeek.dns.trans_id',
+ 'zeek.dns.qclass': 'zeek.dns.qclass',
+ 'zeek.dns.RA': 'zeek.dns.RA',
+ 'zeek.dns.TC': 'zeek.dns.TC',
+ 'zeek.http.resp_mime_types': 'zeek.http.resp_mime_types',
+ 'zeek.http.trans_depth': 'zeek.http.trans_depth',
+ 'zeek.http.status_msg': 'zeek.http.status_msg',
+ 'zeek.http.resp_fuids': 'zeek.http.resp_fuids',
+ 'zeek.http.tags': 'zeek.http.tags',
+ 'zeek.files.session_ids': 'zeek.files.session_ids',
+ 'zeek.files.timedout': 'zeek.files.timedout',
+ 'zeek.files.local_orig': 'zeek.files.local_orig',
+ 'zeek.files.tx_host': 'zeek.files.tx_host',
+ 'zeek.files.source': 'zeek.files.source',
+ 'zeek.files.is_orig': 'zeek.files.is_orig',
+ 'zeek.files.overflow_bytes': 'zeek.files.overflow_bytes',
+ 'zeek.files.sha1': 'zeek.files.sha1',
+ 'zeek.files.duration': 'zeek.files.duration',
+ 'zeek.files.depth': 'zeek.files.depth',
+ 'zeek.files.analyzers': 'zeek.files.analyzers',
+ 'zeek.files.mime_type': 'zeek.files.mime_type',
+ 'zeek.files.rx_host': 'zeek.files.rx_host',
+ 'zeek.files.total_bytes': 'zeek.files.total_bytes',
+ 'zeek.files.fuid': 'zeek.files.fuid',
+ 'zeek.files.seen_bytes': 'zeek.files.seen_bytes',
+ 'zeek.files.missing_bytes': 'zeek.files.missing_bytes',
+ 'zeek.files.md5': 'zeek.files.md5',
+ 'zeek.ssl.cipher': 'zeek.ssl.cipher',
+ 'zeek.ssl.established': 'zeek.ssl.established',
+ 'zeek.ssl.resumed': 'zeek.ssl.resumed',
+ 'zeek.ssl.version': 'zeek.ssl.version',
+};
+
+export const sourceFieldsMap: Readonly> = {
+ 'source.bytes': 'source.bytes',
+ 'source.ip': 'source.ip',
+ 'source.packets': 'source.packets',
+ 'source.port': 'source.port',
+ 'source.domain': 'source.domain',
+ 'source.geo.continent_name': 'source.geo.continent_name',
+ 'source.geo.country_name': 'source.geo.country_name',
+ 'source.geo.country_iso_code': 'source.geo.country_iso_code',
+ 'source.geo.city_name': 'source.geo.city_name',
+ 'source.geo.region_iso_code': 'source.geo.region_iso_code',
+ 'source.geo.region_name': 'source.geo.region_name',
+};
+
+export const destinationFieldsMap: Readonly> = {
+ 'destination.bytes': 'destination.bytes',
+ 'destination.ip': 'destination.ip',
+ 'destination.packets': 'destination.packets',
+ 'destination.port': 'destination.port',
+ 'destination.domain': 'destination.domain',
+ 'destination.geo.continent_name': 'destination.geo.continent_name',
+ 'destination.geo.country_name': 'destination.geo.country_name',
+ 'destination.geo.country_iso_code': 'destination.geo.country_iso_code',
+ 'destination.geo.city_name': 'destination.geo.city_name',
+ 'destination.geo.region_iso_code': 'destination.geo.region_iso_code',
+ 'destination.geo.region_name': 'destination.geo.region_name',
+};
+
+export const networkFieldsMap: Readonly> = {
+ 'network.bytes': 'network.bytes',
+ 'network.community_id': 'network.community_id',
+ 'network.direction': 'network.direction',
+ 'network.packets': 'network.packets',
+ 'network.protocol': 'network.protocol',
+ 'network.transport': 'network.transport',
+};
+
+export const geoFieldsMap: Readonly> = {
+ 'geo.region_name': 'destination.geo.region_name',
+ 'geo.country_iso_code': 'destination.geo.country_iso_code',
+};
+
+export const dnsFieldsMap: Readonly> = {
+ 'dns.question.name': 'dns.question.name',
+ 'dns.question.type': 'dns.question.type',
+ 'dns.resolved_ip': 'dns.resolved_ip',
+ 'dns.response_code': 'dns.response_code',
+};
+
+export const endgameFieldsMap: Readonly> = {
+ 'endgame.exit_code': 'endgame.exit_code',
+ 'endgame.file_name': 'endgame.file_name',
+ 'endgame.file_path': 'endgame.file_path',
+ 'endgame.logon_type': 'endgame.logon_type',
+ 'endgame.parent_process_name': 'endgame.parent_process_name',
+ 'endgame.pid': 'endgame.pid',
+ 'endgame.process_name': 'endgame.process_name',
+ 'endgame.subject_domain_name': 'endgame.subject_domain_name',
+ 'endgame.subject_logon_id': 'endgame.subject_logon_id',
+ 'endgame.subject_user_name': 'endgame.subject_user_name',
+ 'endgame.target_domain_name': 'endgame.target_domain_name',
+ 'endgame.target_logon_id': 'endgame.target_logon_id',
+ 'endgame.target_user_name': 'endgame.target_user_name',
+};
+
+export const eventBaseFieldsMap: Readonly> = {
+ 'event.action': 'event.action',
+ 'event.category': 'event.category',
+ 'event.code': 'event.code',
+ 'event.created': 'event.created',
+ 'event.dataset': 'event.dataset',
+ 'event.duration': 'event.duration',
+ 'event.end': 'event.end',
+ 'event.hash': 'event.hash',
+ 'event.id': 'event.id',
+ 'event.kind': 'event.kind',
+ 'event.module': 'event.module',
+ 'event.original': 'event.original',
+ 'event.outcome': 'event.outcome',
+ 'event.risk_score': 'event.risk_score',
+ 'event.risk_score_norm': 'event.risk_score_norm',
+ 'event.severity': 'event.severity',
+ 'event.start': 'event.start',
+ 'event.timezone': 'event.timezone',
+ 'event.type': 'event.type',
+};
+
+export const systemFieldsMap: Readonly> = {
+ 'system.audit.package.arch': 'system.audit.package.arch',
+ 'system.audit.package.entity_id': 'system.audit.package.entity_id',
+ 'system.audit.package.name': 'system.audit.package.name',
+ 'system.audit.package.size': 'system.audit.package.size',
+ 'system.audit.package.summary': 'system.audit.package.summary',
+ 'system.audit.package.version': 'system.audit.package.version',
+ 'system.auth.ssh.signature': 'system.auth.ssh.signature',
+ 'system.auth.ssh.method': 'system.auth.ssh.method',
+};
+
+export const signalFieldsMap: Readonly> = {
+ 'signal.original_time': 'signal.original_time',
+ 'signal.rule.id': 'signal.rule.id',
+ 'signal.rule.saved_id': 'signal.rule.saved_id',
+ 'signal.rule.timeline_id': 'signal.rule.timeline_id',
+ 'signal.rule.timeline_title': 'signal.rule.timeline_title',
+ 'signal.rule.output_index': 'signal.rule.output_index',
+ 'signal.rule.from': 'signal.rule.from',
+ 'signal.rule.index': 'signal.rule.index',
+ 'signal.rule.language': 'signal.rule.language',
+ 'signal.rule.query': 'signal.rule.query',
+ 'signal.rule.to': 'signal.rule.to',
+ 'signal.rule.filters': 'signal.rule.filters',
+ 'signal.rule.rule_id': 'signal.rule.rule_id',
+ 'signal.rule.false_positives': 'signal.rule.false_positives',
+ 'signal.rule.max_signals': 'signal.rule.max_signals',
+ 'signal.rule.risk_score': 'signal.rule.risk_score',
+ 'signal.rule.description': 'signal.rule.description',
+ 'signal.rule.name': 'signal.rule.name',
+ 'signal.rule.immutable': 'signal.rule.immutable',
+ 'signal.rule.references': 'signal.rule.references',
+ 'signal.rule.severity': 'signal.rule.severity',
+ 'signal.rule.tags': 'signal.rule.tags',
+ 'signal.rule.threat': 'signal.rule.threat',
+ 'signal.rule.type': 'signal.rule.type',
+ 'signal.rule.size': 'signal.rule.size',
+ 'signal.rule.enabled': 'signal.rule.enabled',
+ 'signal.rule.created_at': 'signal.rule.created_at',
+ 'signal.rule.updated_at': 'signal.rule.updated_at',
+ 'signal.rule.created_by': 'signal.rule.created_by',
+ 'signal.rule.updated_by': 'signal.rule.updated_by',
+ 'signal.rule.version': 'signal.rule.version',
+ 'signal.rule.note': 'signal.rule.note',
+ 'signal.rule.threshold': 'signal.rule.threshold',
+ 'signal.rule.exceptions_list': 'signal.rule.exceptions_list',
+};
+
+export const ruleFieldsMap: Readonly> = {
+ 'rule.reference': 'rule.reference',
+};
+
+export const eventFieldsMap: Readonly> = {
+ timestamp: '@timestamp',
+ '@timestamp': '@timestamp',
+ message: 'message',
+ ...{ ...agentFieldsMap },
+ ...{ ...auditdMap },
+ ...{ ...destinationFieldsMap },
+ ...{ ...dnsFieldsMap },
+ ...{ ...endgameFieldsMap },
+ ...{ ...eventBaseFieldsMap },
+ ...{ ...fileMap },
+ ...{ ...geoFieldsMap },
+ ...{ ...hostFieldsMap },
+ ...{ ...networkFieldsMap },
+ ...{ ...ruleFieldsMap },
+ ...{ ...signalFieldsMap },
+ ...{ ...sourceFieldsMap },
+ ...{ ...suricataFieldsMap },
+ ...{ ...systemFieldsMap },
+ ...{ ...tlsFieldsMap },
+ ...{ ...zeekFieldsMap },
+ ...{ ...httpFieldsMap },
+ ...{ ...userFieldsMap },
+ ...{ ...winlogFieldsMap },
+ ...{ ...processFieldsMap },
+};
diff --git a/x-pack/plugins/timelines/common/ecs/endgame/index.ts b/x-pack/plugins/timelines/common/ecs/endgame/index.ts
new file mode 100644
index 0000000000000..f82a9587c75c3
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/endgame/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface EndgameEcs {
+ exit_code?: number[];
+ file_name?: string[];
+ file_path?: string[];
+ logon_type?: number[];
+ parent_process_name?: string[];
+ pid?: number[];
+ process_name?: string[];
+ subject_domain_name?: string[];
+ subject_logon_id?: string[];
+ subject_user_name?: string[];
+ target_domain_name?: string[];
+ target_logon_id?: string[];
+ target_user_name?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/event/index.ts b/x-pack/plugins/timelines/common/ecs/event/index.ts
new file mode 100644
index 0000000000000..4e38bacefd351
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/event/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface EventEcs {
+ action?: string[];
+
+ category?: string[];
+
+ code?: string[];
+
+ created?: string[];
+
+ dataset?: string[];
+
+ duration?: number[];
+
+ end?: string[];
+
+ hash?: string[];
+
+ id?: string[];
+
+ kind?: string[];
+
+ module?: string[];
+
+ original?: string[];
+
+ outcome?: string[];
+
+ risk_score?: number[];
+
+ risk_score_norm?: number[];
+
+ severity?: number[];
+
+ start?: string[];
+
+ timezone?: string[];
+
+ type?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/file/index.ts b/x-pack/plugins/timelines/common/ecs/file/index.ts
new file mode 100644
index 0000000000000..5e409b1095cf5
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/file/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+interface Original {
+ name?: string[];
+ path?: string[];
+}
+
+export interface CodeSignature {
+ subject_name: string[];
+ trusted: string[];
+}
+export interface Ext {
+ code_signature?: CodeSignature[] | CodeSignature;
+ original?: Original;
+}
+export interface Hash {
+ md5?: string[];
+ sha1?: string[];
+ sha256: string[];
+}
+
+export interface FileEcs {
+ name?: string[];
+
+ path?: string[];
+
+ target_path?: string[];
+
+ extension?: string[];
+
+ Ext?: Ext;
+
+ type?: string[];
+
+ device?: string[];
+
+ inode?: string[];
+
+ uid?: string[];
+
+ owner?: string[];
+
+ gid?: string[];
+
+ group?: string[];
+
+ mode?: string[];
+
+ size?: number[];
+
+ mtime?: string[];
+
+ ctime?: string[];
+
+ hash?: Hash;
+}
diff --git a/x-pack/plugins/timelines/common/ecs/geo/index.ts b/x-pack/plugins/timelines/common/ecs/geo/index.ts
new file mode 100644
index 0000000000000..b6bf0f7b8aaad
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/geo/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface GeoEcs {
+ city_name?: string[];
+ continent_name?: string[];
+ country_iso_code?: string[];
+ country_name?: string[];
+ location?: Location;
+ region_iso_code?: string[];
+ region_name?: string[];
+}
+
+export interface Location {
+ lon?: number[];
+ lat?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/host/index.ts b/x-pack/plugins/timelines/common/ecs/host/index.ts
new file mode 100644
index 0000000000000..37032c91fc312
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/host/index.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface HostEcs {
+ architecture?: string[];
+
+ id?: string[];
+
+ ip?: string[];
+
+ mac?: string[];
+
+ name?: string[];
+
+ os?: OsEcs;
+
+ type?: string[];
+}
+
+export interface OsEcs {
+ platform?: string[];
+
+ name?: string[];
+
+ full?: string[];
+
+ family?: string[];
+
+ version?: string[];
+
+ kernel?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/http/index.ts b/x-pack/plugins/timelines/common/ecs/http/index.ts
new file mode 100644
index 0000000000000..89ce6b678181b
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/http/index.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface HttpEcs {
+ version?: string[];
+
+ request?: HttpRequestData;
+
+ response?: HttpResponseData;
+}
+
+export interface HttpRequestData {
+ method?: string[];
+
+ body?: HttpBodyData;
+
+ referrer?: string[];
+
+ bytes?: number[];
+}
+
+export interface HttpBodyData {
+ content?: string[];
+
+ bytes?: number[];
+}
+
+export interface HttpResponseData {
+ status_code?: number[];
+
+ body?: HttpBodyData;
+
+ bytes?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/index.ts b/x-pack/plugins/timelines/common/ecs/index.ts
new file mode 100644
index 0000000000000..8054b3c8521db
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/index.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { AgentEcs } from './agent';
+import { AuditdEcs } from './auditd';
+import { DestinationEcs } from './destination';
+import { DnsEcs } from './dns';
+import { EndgameEcs } from './endgame';
+import { EventEcs } from './event';
+import { FileEcs } from './file';
+import { GeoEcs } from './geo';
+import { HostEcs } from './host';
+import { NetworkEcs } from './network';
+import { RegistryEcs } from './registry';
+import { RuleEcs } from './rule';
+import { SignalEcs } from './signal';
+import { SourceEcs } from './source';
+import { SuricataEcs } from './suricata';
+import { TlsEcs } from './tls';
+import { ZeekEcs } from './zeek';
+import { HttpEcs } from './http';
+import { UrlEcs } from './url';
+import { UserEcs } from './user';
+import { WinlogEcs } from './winlog';
+import { ProcessEcs } from './process';
+import { SystemEcs } from './system';
+import { ThreatEcs } from './threat';
+import { Ransomware } from './ransomware';
+
+export interface Ecs {
+ _id: string;
+ _index?: string;
+ agent?: AgentEcs;
+ auditd?: AuditdEcs;
+ destination?: DestinationEcs;
+ dns?: DnsEcs;
+ endgame?: EndgameEcs;
+ event?: EventEcs;
+ geo?: GeoEcs;
+ host?: HostEcs;
+ network?: NetworkEcs;
+ registry?: RegistryEcs;
+ rule?: RuleEcs;
+ signal?: SignalEcs;
+ source?: SourceEcs;
+ suricata?: SuricataEcs;
+ tls?: TlsEcs;
+ zeek?: ZeekEcs;
+ http?: HttpEcs;
+ url?: UrlEcs;
+ timestamp?: string;
+ message?: string[];
+ user?: UserEcs;
+ winlog?: WinlogEcs;
+ process?: ProcessEcs;
+ file?: FileEcs;
+ system?: SystemEcs;
+ threat?: ThreatEcs;
+ // This should be temporary
+ eql?: { parentId: string; sequenceNumber: string };
+ Ransomware?: Ransomware;
+}
diff --git a/x-pack/plugins/timelines/common/ecs/network/index.ts b/x-pack/plugins/timelines/common/ecs/network/index.ts
new file mode 100644
index 0000000000000..6cc5dacab1e53
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/network/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface NetworkEcs {
+ bytes?: number[];
+ community_id?: string[];
+ direction?: string[];
+ packets?: number[];
+ protocol?: string[];
+ transport?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/process/index.ts b/x-pack/plugins/timelines/common/ecs/process/index.ts
new file mode 100644
index 0000000000000..820ecc5560e6c
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/process/index.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Ext } from '../file';
+
+export interface ProcessEcs {
+ Ext?: Ext;
+ entity_id?: string[];
+ exit_code?: number[];
+ hash?: ProcessHashData;
+ parent?: ProcessParentData;
+ pid?: number[];
+ name?: string[];
+ ppid?: number[];
+ args?: string[];
+ executable?: string[];
+ title?: string[];
+ thread?: Thread;
+ working_directory?: string[];
+}
+
+export interface ProcessHashData {
+ md5?: string[];
+ sha1?: string[];
+ sha256?: string[];
+}
+
+export interface ProcessParentData {
+ name?: string[];
+ pid?: number[];
+}
+
+export interface Thread {
+ id?: number[];
+ start?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/ransomware/index.ts b/x-pack/plugins/timelines/common/ecs/ransomware/index.ts
new file mode 100644
index 0000000000000..1724a264f8a4c
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/ransomware/index.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface Ransomware {
+ feature?: string[];
+ score?: string[];
+ version?: number[];
+ child_pids?: string[];
+ files?: RansomwareFiles;
+}
+
+export interface RansomwareFiles {
+ operation?: string[];
+ entropy?: number[];
+ metrics?: string[];
+ extension?: string[];
+ original?: OriginalRansomwareFiles;
+ path?: string[];
+ data?: string[];
+ score?: number[];
+}
+
+export interface OriginalRansomwareFiles {
+ path?: string[];
+ extension?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/registry/index.ts b/x-pack/plugins/timelines/common/ecs/registry/index.ts
new file mode 100644
index 0000000000000..c756fb139199e
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/registry/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface RegistryEcs {
+ hive?: string[];
+ key?: string[];
+ path?: string[];
+ value?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/rule/index.ts b/x-pack/plugins/timelines/common/ecs/rule/index.ts
new file mode 100644
index 0000000000000..ae7e5064a8ece
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/rule/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface RuleEcs {
+ id?: string[];
+ rule_id?: string[];
+ name?: string[];
+ false_positives?: string[];
+ saved_id?: string[];
+ timeline_id?: string[];
+ timeline_title?: string[];
+ max_signals?: number[];
+ risk_score?: string[];
+ output_index?: string[];
+ description?: string[];
+ from?: string[];
+ immutable?: boolean[];
+ index?: string[];
+ interval?: string[];
+ language?: string[];
+ query?: string[];
+ references?: string[];
+ severity?: string[];
+ tags?: string[];
+ threat?: unknown;
+ threshold?: unknown;
+ type?: string[];
+ size?: string[];
+ to?: string[];
+ enabled?: boolean[];
+ filters?: unknown;
+ created_at?: string[];
+ updated_at?: string[];
+ created_by?: string[];
+ updated_by?: string[];
+ version?: string[];
+ note?: string[];
+ building_block_type?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/signal/index.ts b/x-pack/plugins/timelines/common/ecs/signal/index.ts
new file mode 100644
index 0000000000000..45e1f04d2b405
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/signal/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { RuleEcs } from '../rule';
+
+export interface SignalEcs {
+ rule?: RuleEcs;
+ original_time?: string[];
+ status?: string[];
+ group?: {
+ id?: string[];
+ };
+ threshold_result?: unknown;
+}
diff --git a/x-pack/plugins/timelines/common/ecs/source/index.ts b/x-pack/plugins/timelines/common/ecs/source/index.ts
new file mode 100644
index 0000000000000..10a2025eb43ec
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/source/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { GeoEcs } from '../geo';
+
+export interface SourceEcs {
+ bytes?: number[];
+ ip?: string[];
+ port?: number[];
+ domain?: string[];
+ geo?: GeoEcs;
+ packets?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/suricata/index.ts b/x-pack/plugins/timelines/common/ecs/suricata/index.ts
new file mode 100644
index 0000000000000..5555a40188432
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/suricata/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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface SuricataEcs {
+ eve?: SuricataEveData;
+}
+
+export interface SuricataEveData {
+ alert?: SuricataAlertData;
+
+ flow_id?: number[];
+
+ proto?: string[];
+}
+
+export interface SuricataAlertData {
+ signature?: string[];
+
+ signature_id?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/system/index.ts b/x-pack/plugins/timelines/common/ecs/system/index.ts
new file mode 100644
index 0000000000000..f2313c7884511
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/system/index.ts
@@ -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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface SystemEcs {
+ audit?: AuditEcs;
+
+ auth?: AuthEcs;
+}
+
+export interface AuditEcs {
+ package?: PackageEcs;
+}
+
+export interface PackageEcs {
+ arch?: string[];
+
+ entity_id?: string[];
+
+ name?: string[];
+
+ size?: number[];
+
+ summary?: string[];
+
+ version?: string[];
+}
+
+export interface AuthEcs {
+ ssh?: SshEcs;
+}
+
+export interface SshEcs {
+ method?: string[];
+
+ signature?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/threat/index.ts b/x-pack/plugins/timelines/common/ecs/threat/index.ts
new file mode 100644
index 0000000000000..19923a82dc846
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/threat/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EventEcs } from '../event';
+
+interface ThreatMatchEcs {
+ atomic?: string[];
+ field?: string[];
+ type?: string[];
+}
+
+export interface ThreatIndicatorEcs {
+ matched?: ThreatMatchEcs;
+ event?: EventEcs & { reference?: string[] };
+ provider?: string[];
+ type?: string[];
+}
+
+export interface ThreatEcs {
+ indicator: ThreatIndicatorEcs[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/tls/index.ts b/x-pack/plugins/timelines/common/ecs/tls/index.ts
new file mode 100644
index 0000000000000..f2e6b3d36653d
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/tls/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface TlsEcs {
+ client_certificate?: TlsClientCertificateData;
+
+ fingerprints?: TlsFingerprintsData;
+
+ server_certificate?: TlsServerCertificateData;
+}
+
+export interface TlsClientCertificateData {
+ fingerprint?: FingerprintData;
+}
+
+export interface FingerprintData {
+ sha1?: string[];
+}
+
+export interface TlsFingerprintsData {
+ ja3?: TlsJa3Data;
+}
+
+export interface TlsJa3Data {
+ hash?: string[];
+}
+
+export interface TlsServerCertificateData {
+ fingerprint?: FingerprintData;
+}
diff --git a/x-pack/plugins/timelines/common/ecs/url/index.ts b/x-pack/plugins/timelines/common/ecs/url/index.ts
new file mode 100644
index 0000000000000..ea9dc303108e3
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/url/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface UrlEcs {
+ domain?: string[];
+
+ original?: string[];
+
+ username?: string[];
+
+ password?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/user/index.ts b/x-pack/plugins/timelines/common/ecs/user/index.ts
new file mode 100644
index 0000000000000..b03a8e5e96b41
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/user/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface UserEcs {
+ domain?: string[];
+
+ id?: string[];
+
+ name?: string[];
+
+ full_name?: string[];
+
+ email?: string[];
+
+ hash?: string[];
+
+ group?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/winlog/index.ts b/x-pack/plugins/timelines/common/ecs/winlog/index.ts
new file mode 100644
index 0000000000000..27757d05ba6ec
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/winlog/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface WinlogEcs {
+ event_id?: number[];
+}
diff --git a/x-pack/plugins/timelines/common/ecs/zeek/index.ts b/x-pack/plugins/timelines/common/ecs/zeek/index.ts
new file mode 100644
index 0000000000000..b1a3786ae74aa
--- /dev/null
+++ b/x-pack/plugins/timelines/common/ecs/zeek/index.ts
@@ -0,0 +1,134 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface ZeekEcs {
+ session_id?: string[];
+
+ connection?: ZeekConnectionData;
+
+ notice?: ZeekNoticeData;
+
+ dns?: ZeekDnsData;
+
+ http?: ZeekHttpData;
+
+ files?: ZeekFileData;
+
+ ssl?: ZeekSslData;
+}
+
+export interface ZeekConnectionData {
+ local_resp?: boolean[];
+
+ local_orig?: boolean[];
+
+ missed_bytes?: number[];
+
+ state?: string[];
+
+ history?: string[];
+}
+
+export interface ZeekNoticeData {
+ suppress_for?: number[];
+
+ msg?: string[];
+
+ note?: string[];
+
+ sub?: string[];
+
+ dst?: string[];
+
+ dropped?: boolean[];
+
+ peer_descr?: string[];
+}
+
+export interface ZeekDnsData {
+ AA?: boolean[];
+
+ qclass_name?: string[];
+
+ RD?: boolean[];
+
+ qtype_name?: string[];
+
+ rejected?: boolean[];
+
+ qtype?: string[];
+
+ query?: string[];
+
+ trans_id?: number[];
+
+ qclass?: string[];
+
+ RA?: boolean[];
+
+ TC?: boolean[];
+}
+
+export interface ZeekHttpData {
+ resp_mime_types?: string[];
+
+ trans_depth?: string[];
+
+ status_msg?: string[];
+
+ resp_fuids?: string[];
+
+ tags?: string[];
+}
+
+export interface ZeekFileData {
+ session_ids?: string[];
+
+ timedout?: boolean[];
+
+ local_orig?: boolean[];
+
+ tx_host?: string[];
+
+ source?: string[];
+
+ is_orig?: boolean[];
+
+ overflow_bytes?: number[];
+
+ sha1?: string[];
+
+ duration?: number[];
+
+ depth?: number[];
+
+ analyzers?: string[];
+
+ mime_type?: string[];
+
+ rx_host?: string[];
+
+ total_bytes?: number[];
+
+ fuid?: string[];
+
+ seen_bytes?: number[];
+
+ missing_bytes?: number[];
+
+ md5?: string[];
+}
+
+export interface ZeekSslData {
+ cipher?: string[];
+
+ established?: boolean[];
+
+ resumed?: boolean[];
+
+ version?: string[];
+}
diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts
index c095b6c89627e..05174235c20db 100644
--- a/x-pack/plugins/timelines/common/index.ts
+++ b/x-pack/plugins/timelines/common/index.ts
@@ -5,5 +5,9 @@
* 2.0.
*/
+export * from './types';
+export * from './search_strategy';
+export * from './utils/accessibility';
+
export const PLUGIN_ID = 'timelines';
export const PLUGIN_NAME = 'timelines';
diff --git a/x-pack/plugins/timelines/common/search_strategy/common/index.ts b/x-pack/plugins/timelines/common/search_strategy/common/index.ts
new file mode 100644
index 0000000000000..62c2187e267fa
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/common/index.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import type { estypes } from '@elastic/elasticsearch';
+
+export type Maybe = T | null;
+
+export interface TotalValue {
+ value: number;
+ relation: string;
+}
+
+export interface CursorType {
+ value?: Maybe;
+ tiebreaker?: Maybe;
+}
+
+export interface Inspect {
+ dsl: string[];
+}
+
+export enum Direction {
+ asc = 'asc',
+ desc = 'desc',
+}
+
+export interface SortField {
+ field: Field;
+ direction: Direction;
+}
+
+export interface TimerangeInput {
+ /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */
+ interval: string;
+ /** The end of the timerange */
+ to: string;
+ /** The beginning of the timerange */
+ from: string;
+}
+
+export interface PaginationInputPaginated {
+ /** The activePage parameter defines the page of results you want to fetch */
+ activePage: number;
+ /** The cursorStart parameter defines the start of the results to be displayed */
+ cursorStart: number;
+ /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */
+ fakePossibleCount: number;
+ /** The querySize parameter is the number of items to be returned */
+ querySize: number;
+}
+
+export type DocValueFields = estypes.SearchDocValueField;
+
+export interface TimerangeFilter {
+ range: {
+ [timestamp: string]: {
+ gte: string;
+ lte: string;
+ format: string;
+ };
+ };
+}
+
+export interface Fields {
+ [x: string]: T | Array>;
+}
+
+export interface EventSource {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ [field: string]: any;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export interface EventHit extends estypes.SearchHit> {
+ sort: string[];
+ fields: Fields;
+}
diff --git a/x-pack/plugins/timelines/common/search_strategy/eql/index.ts b/x-pack/plugins/timelines/common/search_strategy/eql/index.ts
new file mode 100644
index 0000000000000..4a361bed64890
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/eql/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { TotalValue } from '../common';
+
+export * from './validation';
+
+export type SearchTypes =
+ | string
+ | string[]
+ | number
+ | number[]
+ | boolean
+ | boolean[]
+ | object
+ | object[]
+ | undefined;
+
+export interface BaseHit {
+ _index: string;
+ _id: string;
+ _source: T;
+ fields?: Record;
+}
+
+export interface EqlSequence {
+ join_keys: SearchTypes[];
+ events: Array>;
+}
+
+export interface EqlSearchResponse {
+ is_partial: boolean;
+ is_running: boolean;
+ took: number;
+ timed_out: boolean;
+ hits: {
+ total: TotalValue;
+ sequences?: Array>;
+ events?: Array>;
+ };
+}
diff --git a/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.mock.ts b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.mock.ts
new file mode 100644
index 0000000000000..b3a2c9c9a3f62
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.mock.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ApiResponse } from '@elastic/elasticsearch';
+import { ErrorResponse } from './helpers';
+
+export const getValidEqlResponse = (): ApiResponse['body'] => ({
+ is_partial: false,
+ is_running: false,
+ took: 162,
+ timed_out: false,
+ hits: {
+ total: {
+ value: 1,
+ relation: 'eq',
+ },
+ sequences: [],
+ },
+});
+
+export const getEqlResponseWithValidationError = (): ErrorResponse => ({
+ error: {
+ root_cause: [
+ {
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+ ],
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+});
+
+export const getEqlResponseWithValidationErrors = (): ErrorResponse => ({
+ error: {
+ root_cause: [
+ {
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+ {
+ type: 'parsing_exception',
+ reason: "line 1:4: mismatched input '' expecting 'where'",
+ },
+ ],
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+});
+
+export const getEqlResponseWithNonValidationError = (): ApiResponse['body'] => ({
+ error: {
+ root_cause: [
+ {
+ type: 'other_error',
+ reason: 'some other reason',
+ },
+ ],
+ type: 'other_error',
+ reason: 'some other reason',
+ },
+});
diff --git a/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.test.ts b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.test.ts
new file mode 100644
index 0000000000000..de75cf6ac6dc7
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.test.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getValidationErrors, isErrorResponse, isValidationErrorResponse } from './helpers';
+import {
+ getEqlResponseWithNonValidationError,
+ getEqlResponseWithValidationError,
+ getEqlResponseWithValidationErrors,
+ getValidEqlResponse,
+} from './helpers.mock';
+
+describe('eql validation helpers', () => {
+ describe('isErrorResponse', () => {
+ it('is false for a regular response', () => {
+ expect(isErrorResponse(getValidEqlResponse())).toEqual(false);
+ });
+
+ it('is true for a response with non-validation errors', () => {
+ expect(isErrorResponse(getEqlResponseWithNonValidationError())).toEqual(true);
+ });
+
+ it('is true for a response with validation errors', () => {
+ expect(isErrorResponse(getEqlResponseWithValidationError())).toEqual(true);
+ });
+ });
+
+ describe('isValidationErrorResponse', () => {
+ it('is false for a regular response', () => {
+ expect(isValidationErrorResponse(getValidEqlResponse())).toEqual(false);
+ });
+
+ it('is false for a response with non-validation errors', () => {
+ expect(isValidationErrorResponse(getEqlResponseWithNonValidationError())).toEqual(false);
+ });
+
+ it('is true for a response with validation errors', () => {
+ expect(isValidationErrorResponse(getEqlResponseWithValidationError())).toEqual(true);
+ });
+ });
+
+ describe('getValidationErrors', () => {
+ it('returns a single error for a single root cause', () => {
+ expect(getValidationErrors(getEqlResponseWithValidationError())).toEqual([
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ ]);
+ });
+
+ it('returns multiple errors for multiple root causes', () => {
+ expect(getValidationErrors(getEqlResponseWithValidationErrors())).toEqual([
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ "line 1:4: mismatched input '' expecting 'where'",
+ ]);
+ });
+ });
+});
diff --git a/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.ts b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.ts
new file mode 100644
index 0000000000000..63a812cad759a
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/eql/validation/helpers.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { get, has } from 'lodash';
+
+const PARSING_ERROR_TYPE = 'parsing_exception';
+const VERIFICATION_ERROR_TYPE = 'verification_exception';
+const MAPPING_ERROR_TYPE = 'mapping_exception';
+
+interface ErrorCause {
+ type: string;
+ reason: string;
+}
+
+export interface ErrorResponse {
+ error: ErrorCause & { root_cause: ErrorCause[] };
+}
+
+const isValidationErrorType = (type: unknown): boolean =>
+ type === PARSING_ERROR_TYPE || type === VERIFICATION_ERROR_TYPE || type === MAPPING_ERROR_TYPE;
+
+export const isErrorResponse = (response: unknown): response is ErrorResponse =>
+ has(response, 'error.type');
+
+export const isValidationErrorResponse = (response: unknown): response is ErrorResponse =>
+ isErrorResponse(response) && isValidationErrorType(get(response, 'error.type'));
+
+export const getValidationErrors = (response: ErrorResponse): string[] =>
+ response.error.root_cause
+ .filter((cause) => isValidationErrorType(cause.type))
+ .map((cause) => cause.reason);
diff --git a/x-pack/plugins/timelines/common/search_strategy/eql/validation/index.ts b/x-pack/plugins/timelines/common/search_strategy/eql/validation/index.ts
new file mode 100644
index 0000000000000..6c315f929b9bb
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/eql/validation/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './helpers';
diff --git a/x-pack/plugins/timelines/common/search_strategy/index.ts b/x-pack/plugins/timelines/common/search_strategy/index.ts
new file mode 100644
index 0000000000000..155306327ee0c
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './common';
+export * from './timeline';
+export * from './index_fields';
+export * from './eql';
diff --git a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts
new file mode 100644
index 0000000000000..76ab48a8243db
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { IIndexPattern } from 'src/plugins/data/public';
+import {
+ IEsSearchRequest,
+ IEsSearchResponse,
+ IFieldSubType,
+} from '../../../../../../src/plugins/data/common';
+import { DocValueFields, Maybe } from '../common';
+
+export type BeatFieldsFactoryQueryType = 'beatFields';
+
+interface FieldInfo {
+ category: string;
+ description?: string;
+ example?: string | number;
+ format?: string;
+ name: string;
+ type?: string;
+}
+
+export interface IndexField {
+ /** Where the field belong */
+ category: string;
+ /** Example of field's value */
+ example?: Maybe;
+ /** whether the field's belong to an alias index */
+ indexes: Array>;
+ /** The name of the field */
+ name: string;
+ /** The type of the field's values as recognized by Kibana */
+ type: string;
+ /** Whether the field's values can be efficiently searched for */
+ searchable: boolean;
+ /** Whether the field's values can be aggregated */
+ aggregatable: boolean;
+ /** Description of the field */
+ description?: Maybe;
+ format?: Maybe;
+ /** the elastic type as mapped in the index */
+ esTypes?: string[];
+ subType?: IFieldSubType;
+ readFromDocValues: boolean;
+}
+
+export type BeatFields = Record;
+
+export interface IndexFieldsStrategyRequest extends IEsSearchRequest {
+ indices: string[];
+ onlyCheckIfIndicesExist: boolean;
+}
+
+export interface IndexFieldsStrategyResponse extends IEsSearchResponse {
+ indexFields: IndexField[];
+ indicesExist: string[];
+}
+
+export interface BrowserField {
+ aggregatable: boolean;
+ category: string;
+ description: string | null;
+ example: string | number | null;
+ fields: Readonly>>;
+ format: string;
+ indexes: string[];
+ name: string;
+ searchable: boolean;
+ type: string;
+ subType?: {
+ [key: string]: unknown;
+ nested?: {
+ path: string;
+ };
+ };
+}
+
+export type BrowserFields = Readonly>>;
+
+export const EMPTY_BROWSER_FIELDS = {};
+export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = [];
+export const EMPTY_INDEX_PATTERN: IIndexPattern = {
+ fields: [],
+ title: '',
+};
diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts
new file mode 100644
index 0000000000000..94f7bc617e2f2
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
+import type { Ecs } from '../../../../ecs';
+import type { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common';
+import type { TimelineRequestOptionsPaginated } from '../..';
+
+export interface TimelineEdges {
+ node: TimelineItem;
+ cursor: CursorType;
+}
+
+export interface TimelineItem {
+ _id: string;
+ _index?: Maybe;
+ data: TimelineNonEcsData[];
+ ecs: Ecs;
+}
+
+export interface TimelineNonEcsData {
+ field: string;
+ value?: Maybe;
+}
+
+export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
+ edges: TimelineEdges[];
+ totalCount: number;
+ pageInfo: Pick;
+ inspect?: Maybe;
+}
+
+export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated {
+ fields: string[] | Array<{ field: string; include_unmapped: boolean }>;
+ fieldRequested: string[];
+ language: 'eql' | 'kuery' | 'lucene';
+ excludeEcsData?: boolean;
+}
diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/common/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/common/index.ts
new file mode 100644
index 0000000000000..4a5bd2c99a0eb
--- /dev/null
+++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/common/index.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Ecs } from '../../../../ecs';
+import { CursorType, Maybe } from '../../../common';
+
+export interface TimelineEdges {
+ node: TimelineItem;
+ cursor: CursorType;
+}
+
+export interface TimelineItem {
+ _id: string;
+ _index?: Maybe