= await this._runEsQuery({
requestId,
requestName: `${layerName} (${requestCount})`,
searchSource,
@@ -291,8 +291,10 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle
features.push(...convertCompositeRespToGeoJson(esResponse, this._descriptor.requestType));
- afterKey = esResponse.aggregations.compositeSplit.after_key;
- if (esResponse.aggregations.compositeSplit.buckets.length < gridsPerRequest) {
+ const aggr = esResponse.aggregations
+ ?.compositeSplit as estypes.AggregationsCompositeBucketAggregate;
+ afterKey = aggr.after_key;
+ if (aggr.buckets.length < gridsPerRequest) {
// Finished because request did not get full resultset back
break;
}
diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts
index 342eb3fe4bd90..be543b3908b68 100644
--- a/x-pack/plugins/reporting/common/constants.ts
+++ b/x-pack/plugins/reporting/common/constants.ts
@@ -7,8 +7,6 @@
export const PLUGIN_ID = 'reporting';
-export const BROWSER_TYPE = 'chromium';
-
export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY =
'xpack.reporting.jobCompletionNotifications';
@@ -93,13 +91,10 @@ export const API_DIAGNOSE_URL = `${API_BASE_URL}/diagnose`;
export const API_GET_ILM_POLICY_STATUS = `${API_BASE_URL}/ilm_policy_status`;
export const API_MIGRATE_ILM_POLICY_URL = `${API_BASE_URL}/deprecations/migrate_ilm_policy`;
+export const API_BASE_URL_V1 = '/api/reporting/v1'; //
export const ILM_POLICY_NAME = 'kibana-reporting';
-// hacky endpoint: download CSV without queueing a report
-export const API_BASE_URL_V1 = '/api/reporting/v1'; //
-export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv_searchsource`;
-
// Management UI route
export const REPORTING_MANAGEMENT_HOME = '/app/management/insightsAndAlerting/reporting';
@@ -109,11 +104,18 @@ export enum JOB_STATUSES {
PROCESSING = 'processing',
COMPLETED = 'completed',
FAILED = 'failed',
- CANCELLED = 'cancelled',
WARNINGS = 'completed_with_warnings',
}
+// Test Subjects
+export const REPORT_TABLE_ID = 'reportJobListing';
+export const REPORT_TABLE_ROW_ID = 'reportJobRow';
+
// Job params require a `version` field as of 7.15.0. For older jobs set with
// automation that have no version value in the job params, we assume the
// intended version is 7.14.0
export const UNVERSIONED_VERSION = '7.14.0';
+
+// hacky endpoint: download CSV without queueing a report
+// FIXME: find a way to make these endpoints "generic" instead of hardcoded, as are the queued report export types
+export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv_searchsource`;
diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts
index e7549ca74fc90..f3a0e9192cf7d 100644
--- a/x-pack/plugins/reporting/common/types.ts
+++ b/x-pack/plugins/reporting/common/types.ts
@@ -50,7 +50,6 @@ export interface TaskRunResult {
size: number;
csv_contains_formulas?: boolean;
max_size_reached?: boolean;
- needs_sorting?: boolean;
warnings?: string[];
}
@@ -121,8 +120,10 @@ export type JobStatus =
| 'processing' // Report job has been claimed and is executing
| 'failed'; // Report was not successful, and all retries are done. Nothing to download.
+// payload for retrieving the error message of a failed job
export interface JobContent {
- content: string;
+ content: TaskRunResult['content'];
+ content_type: false;
}
/*
diff --git a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap
index f1d9d747a7236..3d49e8e695f9b 100644
--- a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap
+++ b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap
@@ -86,7 +86,7 @@ Array [
size="m"
title="The reporting job failed"
>
- this is the completed report data
+ this is the failed report error
diff --git a/x-pack/plugins/reporting/public/lib/job.ts b/x-pack/plugins/reporting/public/lib/job.ts
deleted file mode 100644
index c882e8b92986b..0000000000000
--- a/x-pack/plugins/reporting/public/lib/job.ts
+++ /dev/null
@@ -1,75 +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 { JobId, ReportApiJSON, ReportSource, TaskRunResult } from '../../common/types';
-
-type ReportPayload = ReportSource['payload'];
-
-/*
- * This class represents a report job for the UI
- * It can be instantiated with ReportApiJSON: the response data format for the report job APIs
- */
-export class Job {
- public id: JobId;
- public index: string;
-
- public objectType: ReportPayload['objectType'];
- public title: ReportPayload['title'];
- public isDeprecated: ReportPayload['isDeprecated'];
- public browserTimezone?: ReportPayload['browserTimezone'];
- public layout: ReportPayload['layout'];
-
- public jobtype: ReportSource['jobtype'];
- public created_by: ReportSource['created_by'];
- public created_at: ReportSource['created_at'];
- public started_at: ReportSource['started_at'];
- public completed_at: ReportSource['completed_at'];
- public status: ReportSource['status'];
- public attempts: ReportSource['attempts'];
- public max_attempts: ReportSource['max_attempts'];
-
- public timeout: ReportSource['timeout'];
- public kibana_name: ReportSource['kibana_name'];
- public kibana_id: ReportSource['kibana_id'];
- public browser_type: ReportSource['browser_type'];
-
- public size?: TaskRunResult['size'];
- public content_type?: TaskRunResult['content_type'];
- public csv_contains_formulas?: TaskRunResult['csv_contains_formulas'];
- public max_size_reached?: TaskRunResult['max_size_reached'];
- public warnings?: TaskRunResult['warnings'];
-
- constructor(report: ReportApiJSON) {
- this.id = report.id;
- this.index = report.index;
-
- this.jobtype = report.jobtype;
- this.objectType = report.payload.objectType;
- this.title = report.payload.title;
- this.layout = report.payload.layout;
- this.created_by = report.created_by;
- this.created_at = report.created_at;
- this.started_at = report.started_at;
- this.completed_at = report.completed_at;
- this.status = report.status;
- this.attempts = report.attempts;
- this.max_attempts = report.max_attempts;
-
- this.timeout = report.timeout;
- this.kibana_name = report.kibana_name;
- this.kibana_id = report.kibana_id;
- this.browser_type = report.browser_type;
- this.browserTimezone = report.payload.browserTimezone;
- this.size = report.output?.size;
- this.content_type = report.output?.content_type;
-
- this.isDeprecated = report.payload.isDeprecated || false;
- this.csv_contains_formulas = report.output?.csv_contains_formulas;
- this.max_size_reached = report.output?.max_size_reached;
- this.warnings = report.output?.warnings;
- }
-}
diff --git a/x-pack/plugins/reporting/public/lib/job.tsx b/x-pack/plugins/reporting/public/lib/job.tsx
new file mode 100644
index 0000000000000..96967dc9226c9
--- /dev/null
+++ b/x-pack/plugins/reporting/public/lib/job.tsx
@@ -0,0 +1,269 @@
+/*
+ * 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 { EuiText, EuiTextColor } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import moment from 'moment';
+import React from 'react';
+import { JOB_STATUSES } from '../../common/constants';
+import { JobId, ReportApiJSON, ReportSource, TaskRunResult } from '../../common/types';
+
+const { COMPLETED, FAILED, PENDING, PROCESSING, WARNINGS } = JOB_STATUSES;
+
+type ReportPayload = ReportSource['payload'];
+
+/*
+ * This class represents a report job for the UI
+ * It can be instantiated with ReportApiJSON: the response data format for the report job APIs
+ */
+export class Job {
+ public id: JobId;
+ public index: string;
+
+ public objectType: ReportPayload['objectType'];
+ public title: ReportPayload['title'];
+ public isDeprecated: ReportPayload['isDeprecated'];
+ public browserTimezone?: ReportPayload['browserTimezone'];
+ public layout: ReportPayload['layout'];
+
+ public jobtype: ReportSource['jobtype'];
+ public created_by: ReportSource['created_by'];
+ public created_at: ReportSource['created_at'];
+ public started_at: ReportSource['started_at'];
+ public completed_at: ReportSource['completed_at'];
+ public status: JOB_STATUSES; // FIXME: can not use ReportSource['status'] due to type mismatch
+ public attempts: ReportSource['attempts'];
+ public max_attempts: ReportSource['max_attempts'];
+
+ public timeout: ReportSource['timeout'];
+ public kibana_name: ReportSource['kibana_name'];
+ public kibana_id: ReportSource['kibana_id'];
+ public browser_type: ReportSource['browser_type'];
+
+ public size?: TaskRunResult['size'];
+ public content_type?: TaskRunResult['content_type'];
+ public csv_contains_formulas?: TaskRunResult['csv_contains_formulas'];
+ public max_size_reached?: TaskRunResult['max_size_reached'];
+ public warnings?: TaskRunResult['warnings'];
+
+ constructor(report: ReportApiJSON) {
+ this.id = report.id;
+ this.index = report.index;
+
+ this.jobtype = report.jobtype;
+ this.objectType = report.payload.objectType;
+ this.title = report.payload.title;
+ this.layout = report.payload.layout;
+ this.created_by = report.created_by;
+ this.created_at = report.created_at;
+ this.started_at = report.started_at;
+ this.completed_at = report.completed_at;
+ this.status = report.status as JOB_STATUSES;
+ this.attempts = report.attempts;
+ this.max_attempts = report.max_attempts;
+
+ this.timeout = report.timeout;
+ this.kibana_name = report.kibana_name;
+ this.kibana_id = report.kibana_id;
+ this.browser_type = report.browser_type;
+ this.browserTimezone = report.payload.browserTimezone;
+ this.size = report.output?.size;
+ this.content_type = report.output?.content_type;
+
+ this.isDeprecated = report.payload.isDeprecated || false;
+ this.csv_contains_formulas = report.output?.csv_contains_formulas;
+ this.max_size_reached = report.output?.max_size_reached;
+ this.warnings = report.output?.warnings;
+ }
+
+ getStatusMessage() {
+ const status = this.status;
+ let smallMessage;
+ if (status === PENDING) {
+ smallMessage = i18n.translate('xpack.reporting.jobStatusDetail.pendingStatusReachedText', {
+ defaultMessage: 'Waiting for job to be processed.',
+ });
+ } else if (status === PROCESSING) {
+ smallMessage = i18n.translate('xpack.reporting.jobStatusDetail.attemptXofY', {
+ defaultMessage: 'Attempt {attempts} of {max_attempts}.',
+ values: { attempts: this.attempts, max_attempts: this.max_attempts },
+ });
+ } else if (this.getWarnings()) {
+ smallMessage = i18n.translate('xpack.reporting.jobStatusDetail.warningsText', {
+ defaultMessage: 'See report info for warnings.',
+ });
+ } else if (this.getError()) {
+ smallMessage = i18n.translate('xpack.reporting.jobStatusDetail.errorText', {
+ defaultMessage: 'See report info for error details.',
+ });
+ }
+
+ if (smallMessage) {
+ return (
+
+ {smallMessage}
+
+ );
+ }
+
+ return null;
+ }
+
+ getStatus() {
+ const statusLabel = jobStatusLabelsMap.get(this.status) as string;
+ const statusTimestamp = this.getStatusTimestamp();
+
+ if (statusTimestamp) {
+ return (
+ {this.formatDate(statusTimestamp)}
+ ),
+ }}
+ />
+ );
+ }
+
+ return statusLabel;
+ }
+
+ getStatusLabel() {
+ return (
+ <>
+ {this.getStatus()}
+ {this.getStatusMessage()}
+ >
+ );
+ }
+
+ getCreatedAtLabel() {
+ if (this.created_by) {
+ return (
+ <>
+ {this.formatDate(this.created_at)}
+ {this.created_by}
+ >
+ );
+ }
+ return this.formatDate(this.created_at);
+ }
+
+ /*
+ * We use `output.warnings` to show the error of a failed report job,
+ * and to show warnings of a job that completed with warnings.
+ */
+
+ // There is no error unless the status is 'failed'
+ getError() {
+ if (this.status === FAILED) {
+ return this.warnings;
+ }
+ }
+
+ getWarnings() {
+ if (this.status !== FAILED) {
+ const warnings: string[] = [];
+ if (this.isDeprecated) {
+ warnings.push(
+ i18n.translate('xpack.reporting.jobWarning.exportTypeDeprecated', {
+ defaultMessage:
+ 'This is a deprecated export type. Automation of this report will need to be re-created for compatibility with future versions of Kibana.',
+ })
+ );
+ }
+ if (this.csv_contains_formulas) {
+ warnings.push(
+ i18n.translate('xpack.reporting.jobWarning.csvContainsFormulas', {
+ defaultMessage:
+ 'Your CSV contains characters which spreadsheet applications can interpret as formulas.',
+ })
+ );
+ }
+ if (this.max_size_reached) {
+ warnings.push(
+ i18n.translate('xpack.reporting.jobWarning.maxSizeReachedTooltip', {
+ defaultMessage: 'Max size reached, contains partial data.',
+ })
+ );
+ }
+
+ if (this.warnings?.length) {
+ warnings.push(...this.warnings);
+ }
+
+ if (warnings.length) {
+ return (
+
+ {warnings.map((w, i) => {
+ return {w} ;
+ })}
+
+ );
+ }
+ }
+ }
+
+ private formatDate(timestamp: string) {
+ try {
+ return moment(timestamp).format('YYYY-MM-DD @ hh:mm A');
+ } catch (error) {
+ // ignore parse error and display unformatted value
+ return timestamp;
+ }
+ }
+
+ private getStatusTimestamp() {
+ const status = this.status;
+ if (status === PROCESSING && this.started_at) {
+ return this.started_at;
+ }
+
+ if (this.completed_at && ([COMPLETED, FAILED, WARNINGS] as string[]).includes(status)) {
+ return this.completed_at;
+ }
+
+ return this.created_at;
+ }
+}
+
+const jobStatusLabelsMap = new Map([
+ [
+ PENDING,
+ i18n.translate('xpack.reporting.jobStatuses.pendingText', {
+ defaultMessage: 'Pending',
+ }),
+ ],
+ [
+ PROCESSING,
+ i18n.translate('xpack.reporting.jobStatuses.processingText', {
+ defaultMessage: 'Processing',
+ }),
+ ],
+ [
+ COMPLETED,
+ i18n.translate('xpack.reporting.jobStatuses.completedText', {
+ defaultMessage: 'Completed', // NOTE: a job is `completed` not `completed_with_warings` if it has reached max size or possibly contains csv characters
+ }),
+ ],
+ [
+ WARNINGS,
+ i18n.translate('xpack.reporting.jobStatuses.warningText', {
+ defaultMessage: 'Completed',
+ }),
+ ],
+ [
+ FAILED,
+ i18n.translate('xpack.reporting.jobStatuses.failedText', {
+ defaultMessage: 'Failed',
+ }),
+ ],
+]);
diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts
index 867c8a5e8666f..6c62c560b095d 100644
--- a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts
+++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { stringify } from 'query-string';
import rison, { RisonObject } from 'rison-node';
@@ -27,11 +28,6 @@ import {
import { add } from '../../notifier/job_completion_notifications';
import { Job } from '../job';
-export interface JobContent {
- content: string;
- content_type: boolean;
-}
-
export interface DiagnoseResponse {
help: string[];
success: boolean;
@@ -50,7 +46,7 @@ interface IReportingAPI {
deleteReport(jobId: string): Promise;
list(page: number, jobIds: string[]): Promise; // gets the first 10 report of the page
total(): Promise;
- getError(jobId: string): Promise;
+ getError(jobId: string): Promise;
getInfo(jobId: string): Promise;
findForJobIds(jobIds: string[]): Promise;
@@ -112,8 +108,16 @@ export class ReportingAPIClient implements IReportingAPI {
}
public async getError(jobId: string) {
- return await this.http.get(`${API_LIST_URL}/output/${jobId}`, {
- asSystemRequest: true,
+ const job = await this.getInfo(jobId);
+
+ if (job.warnings?.[0]) {
+ // the error message of a failed report is a singular string in the warnings array
+ return job.warnings[0];
+ }
+
+ return i18n.translate('xpack.reporting.apiClient.unknownError', {
+ defaultMessage: `Report job {job} failed: Unknown error.`,
+ values: { job: jobId },
});
}
diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts
index e20ba0c9f38af..518c8ef11857a 100644
--- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts
+++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts
@@ -28,11 +28,10 @@ const mockJobsFound: Job[] = [
const coreSetup = coreMock.createSetup();
const jobQueueClientMock = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0');
-jobQueueClientMock.findForJobIds = async (jobIds: string[]) => mockJobsFound;
+jobQueueClientMock.findForJobIds = async () => mockJobsFound;
jobQueueClientMock.getInfo = () =>
Promise.resolve(({ content: 'this is the completed report data' } as unknown) as Job);
-jobQueueClientMock.getError = () =>
- Promise.resolve({ content: 'this is the completed report data' });
+jobQueueClientMock.getError = () => Promise.resolve('this is the failed report error');
jobQueueClientMock.getManagementLink = () => '/#management';
jobQueueClientMock.getDownloadLink = () => '/reporting/download/job-123';
diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts
index 8e41d34d054ec..304b4fb73374d 100644
--- a/x-pack/plugins/reporting/public/lib/stream_handler.ts
+++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts
@@ -74,9 +74,9 @@ export class ReportingNotifierStreamHandler {
// no download link available
for (const job of failedJobs) {
- const { content } = await this.apiClient.getError(job.id);
+ const errorText = await this.apiClient.getError(job.id);
this.notifications.toasts.addDanger(
- getFailureToast(content, job, this.apiClient.getManagementLink)
+ getFailureToast(errorText, job, this.apiClient.getManagementLink)
);
}
return { completed: completedJobs, failed: failedJobs };
diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap
index 3417aa59f9d72..4ab50750bbc52 100644
--- a/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap
@@ -176,7 +176,7 @@ Array [
className="euiTitle euiTitle--medium"
id="flyoutTitle"
>
- Job Info
+ Report info
@@ -226,7 +226,7 @@ Array [
className="euiTitle euiTitle--medium"
id="flyoutTitle"
>
- Job Info
+ Report info
diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
index d0ed2d737b584..d78a09bc6af52 100644
--- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
@@ -340,12 +340,14 @@ exports[`ReportListing Report job listing with some items 1`] = `
>
@@ -521,15 +524,47 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
+ 2020-04-14 @ 05:01 PM
+ ,
+ }
+ }
>
- Pending - waiting for job to be processed
+ Pending at
+
+ 2020-04-14 @ 05:01 PM
+
+
+
+
+
+ Waiting for job to be processed.
+
+
+
+
@@ -648,6 +683,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent__hoverItem"
>
@@ -779,25 +815,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
- license$={
- Object {
- "subscribe": [Function],
- }
- }
- navigateToUrl={[MockFunction]}
- pollConfig={
- Object {
- "jobCompletionNotifier": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- "jobsRefresh": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- }
- }
- record={
+ job={
Job {
"attempts": 0,
"browserTimezone": "America/Phoenix",
@@ -831,6 +849,24 @@ exports[`ReportListing Report job listing with some items 1`] = `
"warnings": undefined,
}
}
+ license$={
+ Object {
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
redirect={[MockFunction]}
toasts={
Object {
@@ -852,7 +888,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
}
}
/>
-
-
-
-
-
@@ -1652,12 +1351,14 @@ exports[`ReportListing Report job listing with some items 1`] = `
@@ -1833,14 +1535,15 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -1849,13 +1552,30 @@ exports[`ReportListing Report job listing with some items 1`] = `
}
}
>
- Processing (attempt 1 of 1) at
+ Processing at
2020-04-14 @ 05:01 PM
+
+
+
+
+ Attempt 1 of 1.
+
+
+
+
@@ -1974,6 +1694,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent__hoverItem"
>
@@ -2105,25 +1826,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
- license$={
- Object {
- "subscribe": [Function],
- }
- }
- navigateToUrl={[MockFunction]}
- pollConfig={
- Object {
- "jobCompletionNotifier": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- "jobsRefresh": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- }
- }
- record={
+ job={
Job {
"attempts": 1,
"browserTimezone": "America/Phoenix",
@@ -2157,10 +1860,28 @@ exports[`ReportListing Report job listing with some items 1`] = `
"warnings": undefined,
}
}
- redirect={[MockFunction]}
- toasts={
+ license$={
Object {
- "add": [MockFunction],
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
+ redirect={[MockFunction]}
+ toasts={
+ Object {
+ "add": [MockFunction],
"addDanger": [MockFunction],
"addError": [MockFunction],
"addInfo": [MockFunction],
@@ -2178,7 +1899,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
}
}
/>
-
-
-
-
-
@@ -2978,12 +2362,14 @@ exports[`ReportListing Report job listing with some items 1`] = `
@@ -3159,11 +2546,12 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -3431,25 +2820,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
- license$={
- Object {
- "subscribe": [Function],
- }
- }
- navigateToUrl={[MockFunction]}
- pollConfig={
- Object {
- "jobCompletionNotifier": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- "jobsRefresh": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- }
- }
- record={
+ job={
Job {
"attempts": 1,
"browserTimezone": "America/Phoenix",
@@ -3483,6 +2854,24 @@ exports[`ReportListing Report job listing with some items 1`] = `
"warnings": undefined,
}
}
+ license$={
+ Object {
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
redirect={[MockFunction]}
toasts={
Object {
@@ -3551,7 +2940,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
-
-
-
-
-
@@ -4351,12 +3403,14 @@ exports[`ReportListing Report job listing with some items 1`] = `
@@ -4532,14 +3587,15 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -4548,7 +3604,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
}
}
>
- Completed with warnings at
+ Completed at
@@ -4567,13 +3623,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
-
- Errors occurred: see job info for details.
-
+ See report info for warnings.
@@ -4700,6 +3750,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent__hoverItem"
>
@@ -4831,25 +3882,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
- license$={
- Object {
- "subscribe": [Function],
- }
- }
- navigateToUrl={[MockFunction]}
- pollConfig={
- Object {
- "jobCompletionNotifier": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- "jobsRefresh": Object {
- "interval": 5000,
- "intervalErrorMultiplier": 3,
- },
- }
- }
- record={
+ job={
Job {
"attempts": 1,
"browserTimezone": "America/Phoenix",
@@ -4885,6 +3918,24 @@ exports[`ReportListing Report job listing with some items 1`] = `
],
}
}
+ license$={
+ Object {
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
redirect={[MockFunction]}
toasts={
Object {
@@ -4907,7 +3958,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
}
>
@@ -4953,7 +4004,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Report
-
-
-
-
- My Canvas Workpad
-
-
-
-
-
- canvas workpad
-
-
-
-
-
-
-
-
-
-
-
- Created at
-
-
-
-
- 2020-04-14 @ 01:19 PM
-
-
- elastic
-
-
-
-
-
-
-
-
- Status
-
-
-
-
- 2020-04-14 @ 01:19 PM
- ,
- }
- }
- >
- Completed at
-
- 2020-04-14 @ 01:19 PM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Report
-
-
-
-
- My Canvas Workpad
-
-
-
-
-
- canvas workpad
-
-
-
-
-
-
-
-
-
-
-
- Created at
-
-
-
-
- 2020-04-14 @ 01:19 PM
-
-
- elastic
-
-
-
-
-
-
-
-
- Status
-
-
-
-
- 2020-04-14 @ 01:19 PM
- ,
- }
- }
- >
- Completed at
-
- 2020-04-14 @ 01:19 PM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -8455,16 +4423,16 @@ exports[`ReportListing Report job listing with some items 1`] = `
>
@@ -8503,15 +4471,17 @@ exports[`ReportListing Report job listing with some items 1`] = `
-
- My Canvas Workpad
-
-
-
-
-
- canvas workpad
-
-
-
-
-
-
-
-
-
-
-
- Created at
-
-
-
-
- 2020-04-14 @ 01:17 PM
-
-
- elastic
-
-
-
-
-
-
-
-
- Status
-
-
-
-
- 2020-04-14 @ 01:18 PM
- ,
- }
- }
- >
- Completed at
-
- 2020-04-14 @ 01:18 PM
-
-
-
-
-
-
-
-
-
-
-
+ My Canvas Workpad
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Created at
+
+
+
+
+ 2020-04-14 @ 01:19 PM
+
+
+ elastic
+
+
+
+
+
+
+
+
+ Status
+
+
+
+
+ 2020-04-14 @ 01:19 PM
+ ,
+ }
+ }
+ >
+ Completed at
+
+ 2020-04-14 @ 01:19 PM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9876,15 +5512,17 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-14 @ 01:12 PM
+ 2020-04-14 @ 01:19 PM
elastic
@@ -10030,7 +5669,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-14 @ 01:13 PM
+ 2020-04-14 @ 01:19 PM
,
}
}
@@ -10077,7 +5717,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-14 @ 01:13 PM
+ 2020-04-14 @ 01:19 PM
@@ -10087,7 +5727,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
@@ -10117,12 +5757,12 @@ exports[`ReportListing Report job listing with some items 1`] = `
"attempts": 1,
"browserTimezone": "America/Phoenix",
"browser_type": "chromium",
- "completed_at": "2020-04-14T17:13:03.719Z",
+ "completed_at": "2020-04-14T17:19:36.822Z",
"content_type": "application/pdf",
- "created_at": "2020-04-14T17:12:51.985Z",
+ "created_at": "2020-04-14T17:19:23.578Z",
"created_by": "elastic",
"csv_contains_formulas": undefined,
- "id": "k905zdw11d34cbae0c3y6tzh",
+ "id": "k9067s1m1d4wcbae0cdnvcms",
"index": ".reporting-2020.04.12",
"isDeprecated": false,
"jobtype": "printable_pdf",
@@ -10139,14 +5779,14 @@ exports[`ReportListing Report job listing with some items 1`] = `
"max_size_reached": undefined,
"objectType": "canvas workpad",
"size": 80262,
- "started_at": "2020-04-14T17:12:52.431Z",
+ "started_at": "2020-04-14T17:19:25.247Z",
"status": "completed",
"timeout": 300000,
"title": "My Canvas Workpad",
"warnings": undefined,
}
}
- itemId="k905zdw11d34cbae0c3y6tzh"
+ itemId="k9067s1m1d4wcbae0cdnvcms"
key=".0"
>
@@ -10329,127 +5970,920 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
+ job={
+ Job {
+ "attempts": 1,
+ "browserTimezone": "America/Phoenix",
+ "browser_type": "chromium",
+ "completed_at": "2020-04-14T17:19:36.822Z",
+ "content_type": "application/pdf",
+ "created_at": "2020-04-14T17:19:23.578Z",
+ "created_by": "elastic",
+ "csv_contains_formulas": undefined,
+ "id": "k9067s1m1d4wcbae0cdnvcms",
+ "index": ".reporting-2020.04.12",
+ "isDeprecated": false,
+ "jobtype": "printable_pdf",
+ "kibana_id": "5b2de169-2785-441b-ae8c-186a1936b17d",
+ "kibana_name": "spicy.local",
+ "layout": Object {
+ "dimensions": Object {
+ "height": 720,
+ "width": 1080,
+ },
+ "id": "preserve_layout",
+ },
+ "max_attempts": 1,
+ "max_size_reached": undefined,
+ "objectType": "canvas workpad",
+ "size": 80262,
+ "started_at": "2020-04-14T17:19:25.247Z",
+ "status": "completed",
+ "timeout": 300000,
+ "title": "My Canvas Workpad",
+ "warnings": undefined,
+ }
+ }
+ license$={
+ Object {
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
+ redirect={[MockFunction]}
+ toasts={
+ Object {
+ "add": [MockFunction],
+ "addDanger": [MockFunction],
+ "addError": [MockFunction],
+ "addInfo": [MockFunction],
+ "addSuccess": [MockFunction],
+ "addWarning": [MockFunction],
+ "get$": [MockFunction],
+ "remove": [MockFunction],
+ }
+ }
+ urlService={
+ Object {
+ "locators": Object {
+ "get": [Function],
+ },
+ }
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Report
+
+
+
+
+ My Canvas Workpad
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Created at
+
+
+
+
+ 2020-04-14 @ 01:17 PM
+
+
+ elastic
+
+
+
+
+
+
+
+
+ Status
+
+
+
+
+ 2020-04-14 @ 01:18 PM
+ ,
+ }
+ }
+ >
+ Completed at
+
+ 2020-04-14 @ 01:18 PM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
@@ -11249,15 +7594,17 @@ exports[`ReportListing Report job listing with some items 1`] = `
- count
+ My Canvas Workpad
- visualization
+ canvas workpad
@@ -11362,7 +7709,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-09 @ 03:09 PM
+ 2020-04-14 @ 01:12 PM
elastic
@@ -11403,7 +7751,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-09 @ 03:10 PM
+ 2020-04-14 @ 01:13 PM
,
}
}
@@ -11450,7 +7799,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- 2020-04-09 @ 03:10 PM
+ 2020-04-14 @ 01:13 PM
@@ -11460,7 +7809,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
@@ -11490,36 +7839,36 @@ exports[`ReportListing Report job listing with some items 1`] = `
"attempts": 1,
"browserTimezone": "America/Phoenix",
"browser_type": "chromium",
- "completed_at": "2020-04-09T19:10:10.049Z",
- "content_type": "image/png",
- "created_at": "2020-04-09T19:09:52.139Z",
+ "completed_at": "2020-04-14T17:13:03.719Z",
+ "content_type": "application/pdf",
+ "created_at": "2020-04-14T17:12:51.985Z",
"created_by": "elastic",
"csv_contains_formulas": undefined,
- "id": "k8t4ylcb07mi9d006214ifyg",
- "index": ".reporting-2020.04.05",
+ "id": "k905zdw11d34cbae0c3y6tzh",
+ "index": ".reporting-2020.04.12",
"isDeprecated": false,
- "jobtype": "PNG",
- "kibana_id": "f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914",
+ "jobtype": "printable_pdf",
+ "kibana_id": "5b2de169-2785-441b-ae8c-186a1936b17d",
"kibana_name": "spicy.local",
"layout": Object {
"dimensions": Object {
- "height": 1575,
- "width": 1423,
+ "height": 720,
+ "width": 1080,
},
- "id": "png",
+ "id": "preserve_layout",
},
"max_attempts": 1,
"max_size_reached": undefined,
- "objectType": "visualization",
- "size": 123456789,
- "started_at": "2020-04-09T19:09:54.570Z",
+ "objectType": "canvas workpad",
+ "size": 80262,
+ "started_at": "2020-04-14T17:12:52.431Z",
"status": "completed",
"timeout": 300000,
- "title": "count",
+ "title": "My Canvas Workpad",
"warnings": undefined,
}
}
- itemId="k8t4ylcb07mi9d006214ifyg"
+ itemId="k905zdw11d34cbae0c3y6tzh"
key=".0"
>
@@ -11702,127 +8052,920 @@ exports[`ReportListing Report job listing with some items 1`] = `
"timeZone": null,
}
}
+ job={
+ Job {
+ "attempts": 1,
+ "browserTimezone": "America/Phoenix",
+ "browser_type": "chromium",
+ "completed_at": "2020-04-14T17:13:03.719Z",
+ "content_type": "application/pdf",
+ "created_at": "2020-04-14T17:12:51.985Z",
+ "created_by": "elastic",
+ "csv_contains_formulas": undefined,
+ "id": "k905zdw11d34cbae0c3y6tzh",
+ "index": ".reporting-2020.04.12",
+ "isDeprecated": false,
+ "jobtype": "printable_pdf",
+ "kibana_id": "5b2de169-2785-441b-ae8c-186a1936b17d",
+ "kibana_name": "spicy.local",
+ "layout": Object {
+ "dimensions": Object {
+ "height": 720,
+ "width": 1080,
+ },
+ "id": "preserve_layout",
+ },
+ "max_attempts": 1,
+ "max_size_reached": undefined,
+ "objectType": "canvas workpad",
+ "size": 80262,
+ "started_at": "2020-04-14T17:12:52.431Z",
+ "status": "completed",
+ "timeout": 300000,
+ "title": "My Canvas Workpad",
+ "warnings": undefined,
+ }
+ }
+ license$={
+ Object {
+ "subscribe": [Function],
+ }
+ }
+ navigateToUrl={[MockFunction]}
+ pollConfig={
+ Object {
+ "jobCompletionNotifier": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ "jobsRefresh": Object {
+ "interval": 5000,
+ "intervalErrorMultiplier": 3,
+ },
+ }
+ }
+ redirect={[MockFunction]}
+ toasts={
+ Object {
+ "add": [MockFunction],
+ "addDanger": [MockFunction],
+ "addError": [MockFunction],
+ "addInfo": [MockFunction],
+ "addSuccess": [MockFunction],
+ "addWarning": [MockFunction],
+ "get$": [MockFunction],
+ "remove": [MockFunction],
+ }
+ }
+ urlService={
+ Object {
+ "locators": Object {
+ "get": [Function],
+ },
+ }
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Report
+
+
+
+
+ count
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Created at
+
+
+
+
+ 2020-04-09 @ 03:09 PM
+
+
+ elastic
+
+
+
+
+
+
+
+
+ Status
+
+
+
+
+ 2020-04-09 @ 03:10 PM
+ ,
+ }
+ }
+ >
+ Completed at
+
+ 2020-04-09 @ 03:10 PM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/reporting/public/management/index.ts b/x-pack/plugins/reporting/public/management/index.ts
index c107993ef3074..4d324135288db 100644
--- a/x-pack/plugins/reporting/public/management/index.ts
+++ b/x-pack/plugins/reporting/public/management/index.ts
@@ -5,7 +5,25 @@
* 2.0.
*/
-export { ReportErrorButton } from './report_error_button';
-export { ReportDeleteButton } from './report_delete_button';
-export { ReportDownloadButton } from './report_download_button';
-export { ReportInfoButton } from './report_info_button';
+import { InjectedIntl } from '@kbn/i18n/react';
+import { ApplicationStart, ToastsSetup } from 'src/core/public';
+import { LicensingPluginSetup } from '../../../licensing/public';
+import { UseIlmPolicyStatusReturn } from '../lib/ilm_policy_status_context';
+import { ReportingAPIClient } from '../lib/reporting_api_client';
+import { ClientConfigType } from '../plugin';
+import type { SharePluginSetup } from '../shared_imports';
+
+export interface ListingProps {
+ intl: InjectedIntl;
+ apiClient: ReportingAPIClient;
+ capabilities: ApplicationStart['capabilities'];
+ license$: LicensingPluginSetup['license$']; // FIXME: license$ is deprecated
+ pollConfig: ClientConfigType['poll'];
+ redirect: ApplicationStart['navigateToApp'];
+ navigateToUrl: ApplicationStart['navigateToUrl'];
+ toasts: ToastsSetup;
+ urlService: SharePluginSetup['url'];
+ ilmPolicyContextValue: UseIlmPolicyStatusReturn;
+}
+
+export { ReportListing } from './report_listing';
diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx
index 20ea2988f3b8b..0f0c06f830205 100644
--- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx
+++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx
@@ -16,7 +16,7 @@ import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context
import { ClientConfigType } from '../plugin';
import type { ManagementAppMountParams, SharePluginSetup } from '../shared_imports';
import { KibanaContextProvider } from '../shared_imports';
-import { ReportListing } from './report_listing';
+import { ReportListing } from '.';
export async function mountManagementSection(
coreSetup: CoreSetup,
diff --git a/x-pack/plugins/reporting/public/management/report_delete_button.tsx b/x-pack/plugins/reporting/public/management/report_delete_button.tsx
index 7009a653c1bf6..da1ce9dd9e1cb 100644
--- a/x-pack/plugins/reporting/public/management/report_delete_button.tsx
+++ b/x-pack/plugins/reporting/public/management/report_delete_button.tsx
@@ -8,7 +8,7 @@
import { EuiButton, EuiConfirmModal } from '@elastic/eui';
import React, { Fragment, PureComponent } from 'react';
import { Job } from '../lib/job';
-import { Props as ListingProps } from './report_listing';
+import { ListingProps } from './';
type DeleteFn = () => Promise;
type Props = { jobsToDelete: Job[]; performDelete: DeleteFn } & ListingProps;
diff --git a/x-pack/plugins/reporting/public/management/report_download_button.tsx b/x-pack/plugins/reporting/public/management/report_download_button.tsx
index b421271037722..f21c83fbf42da 100644
--- a/x-pack/plugins/reporting/public/management/report_download_button.tsx
+++ b/x-pack/plugins/reporting/public/management/report_download_button.tsx
@@ -6,23 +6,28 @@
*/
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { InjectedIntl } from '@kbn/i18n/react';
import React, { FunctionComponent } from 'react';
import { JOB_STATUSES } from '../../common/constants';
import { Job as ListingJob } from '../lib/job';
-import { Props as ListingProps } from './report_listing';
+import { ReportingAPIClient } from '../lib/reporting_api_client';
-type Props = { record: ListingJob } & ListingProps;
+interface Props {
+ intl: InjectedIntl;
+ apiClient: ReportingAPIClient;
+ job: ListingJob;
+}
export const ReportDownloadButton: FunctionComponent = (props: Props) => {
- const { record, apiClient, intl } = props;
+ const { job, apiClient, intl } = props;
- if (record.status !== JOB_STATUSES.COMPLETED && record.status !== JOB_STATUSES.WARNINGS) {
+ if (job.status !== JOB_STATUSES.COMPLETED && job.status !== JOB_STATUSES.WARNINGS) {
return null;
}
const button = (
apiClient.downloadReport(record.id)}
+ onClick={() => apiClient.downloadReport(job.id)}
iconType="importAction"
aria-label={intl.formatMessage({
id: 'xpack.reporting.listing.table.downloadReportAriaLabel',
@@ -31,28 +36,14 @@ export const ReportDownloadButton: FunctionComponent = (props: Props) =>
/>
);
- if (record.csv_contains_formulas) {
+ const warnings = job.getWarnings();
+ if (warnings) {
return (
- {button}
-
- );
- }
-
- if (record.max_size_reached) {
- return (
-
{button}
diff --git a/x-pack/plugins/reporting/public/management/report_error_button.tsx b/x-pack/plugins/reporting/public/management/report_error_button.tsx
deleted file mode 100644
index ee0c0e162cb7d..0000000000000
--- a/x-pack/plugins/reporting/public/management/report_error_button.tsx
+++ /dev/null
@@ -1,124 +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 { EuiButtonIcon, EuiCallOut, EuiPopover } from '@elastic/eui';
-import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
-import React, { Component } from 'react';
-import { JOB_STATUSES } from '../../common/constants';
-import { Job as ListingJob } from '../lib/job';
-import { JobContent, ReportingAPIClient } from '../lib/reporting_api_client';
-
-interface Props {
- intl: InjectedIntl;
- apiClient: ReportingAPIClient;
- record: ListingJob;
-}
-
-interface State {
- isLoading: boolean;
- isPopoverOpen: boolean;
- calloutTitle: string;
- error?: string;
-}
-
-class ReportErrorButtonUi extends Component {
- private mounted?: boolean;
-
- constructor(props: Props) {
- super(props);
-
- this.state = {
- isLoading: false,
- isPopoverOpen: false,
- calloutTitle: props.intl.formatMessage({
- id: 'xpack.reporting.errorButton.unableToGenerateReportTitle',
- defaultMessage: 'Unable to generate report',
- }),
- };
- }
-
- public render() {
- const { record, intl } = this.props;
-
- if (record.status !== JOB_STATUSES.FAILED) {
- return null;
- }
-
- const button = (
-
- );
-
- return (
-
-
- {this.state.error}
-
-
- );
- }
-
- public componentWillUnmount() {
- this.mounted = false;
- }
-
- public componentDidMount() {
- this.mounted = true;
- }
-
- private togglePopover = () => {
- this.setState((prevState) => {
- return { isPopoverOpen: !prevState.isPopoverOpen };
- });
-
- if (!this.state.error) {
- this.loadError();
- }
- };
-
- private closePopover = () => {
- this.setState({ isPopoverOpen: false });
- };
-
- private loadError = async () => {
- const { record, apiClient, intl } = this.props;
-
- this.setState({ isLoading: true });
- try {
- const reportContent: JobContent = await apiClient.getError(record.id);
- if (this.mounted) {
- this.setState({ isLoading: false, error: reportContent.content });
- }
- } catch (kfetchError) {
- if (this.mounted) {
- this.setState({
- isLoading: false,
- calloutTitle: intl.formatMessage({
- id: 'xpack.reporting.errorButton.unableToFetchReportContentTitle',
- defaultMessage: 'Unable to fetch report content',
- }),
- error: kfetchError.message,
- });
- }
- }
- };
-}
-
-export const ReportErrorButton = injectI18n(ReportErrorButtonUi);
diff --git a/x-pack/plugins/reporting/public/management/report_info_button.test.tsx b/x-pack/plugins/reporting/public/management/report_info_button.test.tsx
index bbb56340075cc..c52027355ac5e 100644
--- a/x-pack/plugins/reporting/public/management/report_info_button.test.tsx
+++ b/x-pack/plugins/reporting/public/management/report_info_button.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import { coreMock } from '../../../../../src/core/public/mocks';
+import { Job } from '../lib/job';
import { ReportInfoButton } from './report_info_button';
jest.mock('../lib/reporting_api_client');
@@ -17,15 +18,38 @@ import { ReportingAPIClient } from '../lib/reporting_api_client';
const coreSetup = coreMock.createSetup();
const apiClient = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0');
+const job = new Job({
+ id: 'abc-123',
+ index: '.reporting-2020.04.12',
+ migration_version: '7.15.0',
+ attempts: 0,
+ browser_type: 'chromium',
+ created_at: '2020-04-14T21:01:13.064Z',
+ created_by: 'elastic',
+ jobtype: 'printable_pdf',
+ max_attempts: 1,
+ meta: { layout: 'preserve_layout', objectType: 'canvas workpad' },
+ payload: {
+ browserTimezone: 'America/Phoenix',
+ version: '7.15.0-test',
+ layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' },
+ objectType: 'canvas workpad',
+ title: 'My Canvas Workpad',
+ },
+ process_expiration: '1970-01-01T00:00:00.000Z',
+ status: 'pending',
+ timeout: 300000,
+});
+
describe('ReportInfoButton', () => {
it('handles button click flyout on click', () => {
- const wrapper = mountWithIntl( );
+ const wrapper = mountWithIntl( );
const input = wrapper.find('[data-test-subj="reportInfoButton"]').hostNodes();
expect(input).toMatchSnapshot();
});
it('opens flyout with info', async () => {
- const wrapper = mountWithIntl( );
+ const wrapper = mountWithIntl( );
const input = wrapper.find('[data-test-subj="reportInfoButton"]').hostNodes();
input.simulate('click');
@@ -34,7 +58,7 @@ describe('ReportInfoButton', () => {
expect(flyout).toMatchSnapshot();
expect(apiClient.getInfo).toHaveBeenCalledTimes(1);
- expect(apiClient.getInfo).toHaveBeenCalledWith('abc-456');
+ expect(apiClient.getInfo).toHaveBeenCalledWith('abc-123');
});
it('opens flyout with fetch error info', () => {
@@ -43,7 +67,7 @@ describe('ReportInfoButton', () => {
throw new Error('Could not fetch the job info');
});
- const wrapper = mountWithIntl( );
+ const wrapper = mountWithIntl( );
const input = wrapper.find('[data-test-subj="reportInfoButton"]').hostNodes();
input.simulate('click');
@@ -52,6 +76,6 @@ describe('ReportInfoButton', () => {
expect(flyout).toMatchSnapshot();
expect(apiClient.getInfo).toHaveBeenCalledTimes(1);
- expect(apiClient.getInfo).toHaveBeenCalledWith('abc-789');
+ expect(apiClient.getInfo).toHaveBeenCalledWith('abc-123');
});
});
diff --git a/x-pack/plugins/reporting/public/management/report_info_button.tsx b/x-pack/plugins/reporting/public/management/report_info_button.tsx
index 92acaa386bd56..8513558fb89cc 100644
--- a/x-pack/plugins/reporting/public/management/report_info_button.tsx
+++ b/x-pack/plugins/reporting/public/management/report_info_button.tsx
@@ -22,11 +22,11 @@ import React, { Component } from 'react';
import { USES_HEADLESS_JOB_TYPES } from '../../common/constants';
import { Job } from '../lib/job';
import { ReportingAPIClient } from '../lib/reporting_api_client';
-import { Props as ListingProps } from './report_listing';
+import { ListingProps } from '.';
interface Props extends Pick {
- jobId: string;
apiClient: ReportingAPIClient;
+ job: Job;
}
interface State {
@@ -58,7 +58,10 @@ class ReportInfoButtonUi extends Component {
this.state = {
isLoading: false,
isFlyoutVisible: false,
- calloutTitle: 'Job Info',
+ calloutTitle: props.intl.formatMessage({
+ id: 'xpack.reporting.listing.table.reportCalloutTitle',
+ defaultMessage: 'Report info',
+ }),
info: null,
error: null,
};
@@ -76,58 +79,177 @@ class ReportInfoButtonUi extends Component {
return null;
}
- const jobType = info.jobtype || NA;
- const attempts = info.attempts ? info.attempts.toString() : NA;
- const maxAttempts = info.max_attempts ? info.max_attempts.toString() : NA;
const timeout = info.timeout ? info.timeout.toString() : NA;
- const warnings = info.warnings?.join(',') ?? null;
const jobInfo = [
- { title: 'Title', description: info.title || NA },
- { title: 'Created By', description: info.created_by || NA },
- { title: 'Created At', description: info.created_at || NA },
- { title: 'Timezone', description: info.browserTimezone || NA },
- { title: 'Status', description: info.status || NA },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.titleInfo',
+ defaultMessage: 'Title',
+ }),
+ description: info.title || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.createdAtInfo',
+ defaultMessage: 'Created At',
+ }),
+ description: info.getCreatedAtLabel(),
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.statusInfo',
+ defaultMessage: 'Status',
+ }),
+ description: info.getStatus(),
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.tzInfo',
+ defaultMessage: 'Timezone',
+ }),
+ description: info.browserTimezone || NA,
+ },
];
const processingInfo = [
- { title: 'Started At', description: info.started_at || NA },
- { title: 'Completed At', description: info.completed_at || NA },
{
- title: 'Processed By',
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.startedAtInfo',
+ defaultMessage: 'Started At',
+ }),
+ description: info.started_at || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.completedAtInfo',
+ defaultMessage: 'Completed At',
+ }),
+ description: info.completed_at || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.processedByInfo',
+ defaultMessage: 'Processed By',
+ }),
description:
info.kibana_name && info.kibana_id ? `${info.kibana_name} (${info.kibana_id})` : NA,
},
- { title: 'Content Type', description: info.content_type || NA },
- { title: 'Size in Bytes', description: info.size?.toString() || NA },
- { title: 'Attempts', description: attempts },
- { title: 'Max Attempts', description: maxAttempts },
- { title: 'Timeout', description: timeout },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.contentTypeInfo',
+ defaultMessage: 'Content Type',
+ }),
+ description: info.content_type || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.sizeInfo',
+ defaultMessage: 'Size in Bytes',
+ }),
+ description: info.size?.toString() || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.attemptsInfo',
+ defaultMessage: 'Attempts',
+ }),
+ description: info.attempts.toString(),
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.maxAttemptsInfo',
+ defaultMessage: 'Max Attempts',
+ }),
+ description: info.max_attempts?.toString() || NA,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.timeoutInfo',
+ defaultMessage: 'Timeout',
+ }),
+ description: timeout,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.exportTypeInfo',
+ defaultMessage: 'Export Type',
+ }),
+ description: info.isDeprecated
+ ? this.props.intl.formatMessage(
+ {
+ id: 'xpack.reporting.listing.table.reportCalloutExportTypeDeprecated',
+ defaultMessage: '{jobtype} (DEPRECATED)',
+ },
+ { jobtype: info.jobtype }
+ )
+ : info.jobtype,
+ },
+
+ // TODO when https://github.com/elastic/kibana/pull/106137 is merged, add kibana version field
];
const jobScreenshot = [
- { title: 'Dimensions', description: getDimensions(info) },
- { title: 'Layout', description: info.layout?.id || UNKNOWN },
- { title: 'Browser Type', description: info.browser_type || NA },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.dimensionsInfo',
+ defaultMessage: 'Dimensions',
+ }),
+ description: getDimensions(info),
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.layoutInfo',
+ defaultMessage: 'Layout',
+ }),
+ description: info.layout?.id || UNKNOWN,
+ },
+ {
+ title: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.infoPanel.browserTypeInfo',
+ defaultMessage: 'Browser Type',
+ }),
+ description: info.browser_type || NA,
+ },
+ ];
+
+ const warnings = info.getWarnings();
+ const warningsInfo = warnings && [
+ {
+ title: Warnings ,
+ description: {warnings} ,
+ },
];
- const warningInfo = warnings && [{ title: 'Errors', description: warnings }];
+ const errored = info.getError();
+ const errorInfo = errored && [
+ {
+ title: Error ,
+ description: {errored} ,
+ },
+ ];
return (
<>
- {USES_HEADLESS_JOB_TYPES.includes(jobType) ? (
+ {USES_HEADLESS_JOB_TYPES.includes(info.jobtype) ? (
<>
>
) : null}
- {warningInfo ? (
+ {warningsInfo ? (
+ <>
+
+
+ >
+ ) : null}
+ {errorInfo ? (
<>
-
+
>
) : null}
>
@@ -143,6 +265,7 @@ class ReportInfoButtonUi extends Component {
}
public render() {
+ const job = this.props.job;
let flyout;
if (this.state.isFlyoutVisible) {
@@ -168,21 +291,44 @@ class ReportInfoButtonUi extends Component {
);
}
+ let message = this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.table.reportInfoButtonTooltip',
+ defaultMessage: 'See report info',
+ });
+ if (job.getError()) {
+ message = this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.table.reportInfoAndErrorButtonTooltip',
+ defaultMessage: 'See report info and error message',
+ });
+ } else if (job.getWarnings()) {
+ message = this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.table.reportInfoAndWarningsButtonTooltip',
+ defaultMessage: 'See report info and warnings',
+ });
+ }
+
+ let buttonIconType = 'iInCircle';
+ let buttonColor: 'primary' | 'danger' | 'warning' = 'primary';
+ if (job.getWarnings() || job.getError()) {
+ buttonIconType = 'alert';
+ buttonColor = 'danger';
+ }
+ if (job.getWarnings()) {
+ buttonColor = 'warning';
+ }
+
return (
<>
-
+
{flyout}
@@ -193,7 +339,7 @@ class ReportInfoButtonUi extends Component {
private loadInfo = async () => {
this.setState({ isLoading: true });
try {
- const info = await this.props.apiClient.getInfo(this.props.jobId);
+ const info = await this.props.apiClient.getInfo(this.props.job.id);
if (this.mounted) {
this.setState({ isLoading: false, info });
}
@@ -201,7 +347,10 @@ class ReportInfoButtonUi extends Component {
if (this.mounted) {
this.setState({
isLoading: false,
- calloutTitle: 'Unable to fetch report info',
+ calloutTitle: this.props.intl.formatMessage({
+ id: 'xpack.reporting.listing.table.reportInfoUnableToFetch',
+ defaultMessage: 'Unable to fetch report info',
+ }),
info: null,
error: err,
});
diff --git a/x-pack/plugins/reporting/public/management/report_listing.test.tsx b/x-pack/plugins/reporting/public/management/report_listing.test.tsx
index a6bea84174b26..dd8b60801066f 100644
--- a/x-pack/plugins/reporting/public/management/report_listing.test.tsx
+++ b/x-pack/plugins/reporting/public/management/report_listing.test.tsx
@@ -23,7 +23,7 @@ import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context
import { Job } from '../lib/job';
import { InternalApiClientClientProvider, ReportingAPIClient } from '../lib/reporting_api_client';
import { KibanaContextProvider } from '../shared_imports';
-import { Props, ReportListing } from './report_listing';
+import { ListingProps as Props, ReportListing } from '.';
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
return {
diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx
index 9ba0026999137..4e183380a6b41 100644
--- a/x-pack/plugins/reporting/public/management/report_listing.tsx
+++ b/x-pack/plugins/reporting/public/management/report_listing.tsx
@@ -16,39 +16,25 @@ import {
EuiTextColor,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
-import moment from 'moment';
+import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { Component, default as React, Fragment } from 'react';
import { Subscription } from 'rxjs';
-import { ApplicationStart, ToastsSetup } from 'src/core/public';
-import { ILicense, LicensingPluginSetup } from '../../../licensing/public';
-import { JOB_STATUSES as JobStatuses } from '../../common/constants';
+import { ILicense } from '../../../licensing/public';
+import { REPORT_TABLE_ID, REPORT_TABLE_ROW_ID } from '../../common/constants';
import { Poller } from '../../common/poller';
import { durationToNumber } from '../../common/schema_utils';
-import { useIlmPolicyStatus, UseIlmPolicyStatusReturn } from '../lib/ilm_policy_status_context';
+import { useIlmPolicyStatus } from '../lib/ilm_policy_status_context';
import { Job } from '../lib/job';
import { checkLicense } from '../lib/license_check';
-import { ReportingAPIClient, useInternalApiClient } from '../lib/reporting_api_client';
-import { ClientConfigType } from '../plugin';
-import type { SharePluginSetup } from '../shared_imports';
+import { useInternalApiClient } from '../lib/reporting_api_client';
import { useKibana } from '../shared_imports';
-import { ReportDeleteButton, ReportDownloadButton, ReportErrorButton, ReportInfoButton } from './';
import { IlmPolicyLink } from './ilm_policy_link';
import { MigrateIlmPolicyCallOut } from './migrate_ilm_policy_callout';
+import { ReportDeleteButton } from './report_delete_button';
import { ReportDiagnostic } from './report_diagnostic';
-
-export interface Props {
- intl: InjectedIntl;
- apiClient: ReportingAPIClient;
- capabilities: ApplicationStart['capabilities'];
- license$: LicensingPluginSetup['license$'];
- pollConfig: ClientConfigType['poll'];
- redirect: ApplicationStart['navigateToApp'];
- navigateToUrl: ApplicationStart['navigateToUrl'];
- toasts: ToastsSetup;
- urlService: SharePluginSetup['url'];
- ilmPolicyContextValue: UseIlmPolicyStatusReturn;
-}
+import { ReportDownloadButton } from './report_download_button';
+import { ReportInfoButton } from './report_info_button';
+import { ListingProps as Props } from './';
interface State {
page: number;
@@ -61,45 +47,6 @@ interface State {
badLicenseMessage: string;
}
-const jobStatusLabelsMap = new Map([
- [
- JobStatuses.PENDING,
- i18n.translate('xpack.reporting.jobStatuses.pendingText', {
- defaultMessage: 'Pending',
- }),
- ],
- [
- JobStatuses.PROCESSING,
- i18n.translate('xpack.reporting.jobStatuses.processingText', {
- defaultMessage: 'Processing',
- }),
- ],
- [
- JobStatuses.COMPLETED,
- i18n.translate('xpack.reporting.jobStatuses.completedText', {
- defaultMessage: 'Completed',
- }),
- ],
- [
- JobStatuses.WARNINGS,
- i18n.translate('xpack.reporting.jobStatuses.warningText', {
- defaultMessage: 'Completed with warnings',
- }),
- ],
- [
- JobStatuses.FAILED,
- i18n.translate('xpack.reporting.jobStatuses.failedText', {
- defaultMessage: 'Failed',
- }),
- ],
- [
- JobStatuses.CANCELLED,
- i18n.translate('xpack.reporting.jobStatuses.cancelledText', {
- defaultMessage: 'Cancelled',
- }),
- ],
-]);
-
class ReportListingUi extends Component {
private isInitialJobsFetch: boolean;
private licenseSubscription?: Subscription;
@@ -212,9 +159,9 @@ class ReportListingUi extends Component {
this.setState((current) => ({ ...current, selectedJobs: jobs }));
};
- private removeRecord = (record: Job) => {
+ private removeJob = (job: Job) => {
const { jobs } = this.state;
- const filtered = jobs.filter((j) => j.id !== record.id);
+ const filtered = jobs.filter((j) => j.id !== job.id);
this.setState((current) => ({ ...current, jobs: filtered }));
};
@@ -223,17 +170,17 @@ class ReportListingUi extends Component {
if (selectedJobs.length === 0) return undefined;
const performDelete = async () => {
- for (const record of selectedJobs) {
+ for (const job of selectedJobs) {
try {
- await this.props.apiClient.deleteReport(record.id);
- this.removeRecord(record);
+ await this.props.apiClient.deleteReport(job.id);
+ this.removeJob(job);
this.props.toasts.addSuccess(
this.props.intl.formatMessage(
{
id: 'xpack.reporting.listing.table.deleteConfim',
defaultMessage: `The {reportTitle} report was deleted`,
},
- { reportTitle: record.title }
+ { reportTitle: job.title }
)
);
} catch (error) {
@@ -316,15 +263,6 @@ class ReportListingUi extends Component {
return this.state.showLinks && this.state.enableLinks;
};
- private formatDate(timestamp: string) {
- try {
- return moment(timestamp).format('YYYY-MM-DD @ hh:mm A');
- } catch (error) {
- // ignore parse error and display unformatted value
- return timestamp;
- }
- }
-
private renderTable() {
const { intl } = this.props;
@@ -335,12 +273,12 @@ class ReportListingUi extends Component {
id: 'xpack.reporting.listing.tableColumns.reportTitle',
defaultMessage: 'Report',
}),
- render: (objectTitle: string, record: Job) => {
+ render: (objectTitle: string, job: Job) => {
return (
{objectTitle}
- {record.objectType}
+ {job.objectType}
);
@@ -352,17 +290,9 @@ class ReportListingUi extends Component {
id: 'xpack.reporting.listing.tableColumns.createdAtTitle',
defaultMessage: 'Created at',
}),
- render: (createdAt: string, record: Job) => {
- if (record.created_by) {
- return (
-
-
{this.formatDate(createdAt)}
-
{record.created_by}
-
- );
- }
- return this.formatDate(createdAt);
- },
+ render: (_createdAt: string, job: Job) => (
+ {job.getCreatedAtLabel()}
+ ),
},
{
field: 'status',
@@ -370,89 +300,9 @@ class ReportListingUi extends Component {
id: 'xpack.reporting.listing.tableColumns.statusTitle',
defaultMessage: 'Status',
}),
- render: (status: string, record: Job) => {
- if (status === 'pending') {
- return (
-
-
-
- );
- }
-
- let maxSizeReached;
- if (record.max_size_reached) {
- maxSizeReached = (
-
-
-
- );
- }
-
- let warnings;
- if (record.warnings) {
- warnings = (
-
-
-
-
-
- );
- }
-
- let statusTimestamp;
- if (status === JobStatuses.PROCESSING && record.started_at) {
- statusTimestamp = this.formatDate(record.started_at);
- } else if (
- record.completed_at &&
- ([
- JobStatuses.COMPLETED,
- JobStatuses.FAILED,
- JobStatuses.WARNINGS,
- ] as string[]).includes(status)
- ) {
- statusTimestamp = this.formatDate(record.completed_at);
- }
-
- let statusLabel = jobStatusLabelsMap.get(status as JobStatuses) || status;
-
- if (status === JobStatuses.PROCESSING) {
- statusLabel = statusLabel + ` (attempt ${record.attempts} of ${record.max_attempts})`;
- }
-
- if (statusTimestamp) {
- return (
-
- {statusTimestamp},
- }}
- />
- {maxSizeReached}
- {warnings}
-
- );
- }
-
- // unknown status
- return (
-
- {statusLabel}
- {maxSizeReached}
-
- );
- },
+ render: (_status: string, job: Job) => (
+ {job.getStatusLabel()}
+ ),
},
{
name: intl.formatMessage({
@@ -461,12 +311,11 @@ class ReportListingUi extends Component {
}),
actions: [
{
- render: (record: Job) => {
+ render: (job: Job) => {
return (
-
-
-
-
+
+
+
);
},
@@ -520,7 +369,8 @@ class ReportListingUi extends Component
{
selection={selection}
isSelectable={true}
onChange={this.onTableChange}
- data-test-subj="reportJobListing"
+ data-test-subj={REPORT_TABLE_ID}
+ rowProps={() => ({ 'data-test-subj': REPORT_TABLE_ROW_ID })}
/>
);
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts
index eb2abf4036c03..7aaa9c78602a9 100644
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts
+++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts
@@ -17,7 +17,6 @@ import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators';
import { getChromiumDisconnectedError } from '../';
import { ReportingCore } from '../../..';
-import { BROWSER_TYPE } from '../../../../common/constants';
import { durationToNumber } from '../../../../common/schema_utils';
import { CaptureConfig } from '../../../../server/types';
import { LevelLogger } from '../../../lib';
@@ -70,7 +69,7 @@ export class HeadlessChromiumDriverFactory {
});
}
- type = BROWSER_TYPE;
+ type = 'chromium';
/*
* Return an observable to objects which will drive screenshot capture for a page
diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts
index 0d0332983d6bc..37557c3afb0c7 100644
--- a/x-pack/plugins/reporting/server/routes/jobs.ts
+++ b/x-pack/plugins/reporting/server/routes/jobs.ts
@@ -93,49 +93,6 @@ export function registerJobInfoRoutes(reporting: ReportingCore) {
})
);
- // return the raw output from a job
- router.get(
- {
- path: `${MAIN_ENTRY}/output/{docId}`,
- validate: {
- params: schema.object({
- docId: schema.string({ minLength: 2 }),
- }),
- },
- },
- userHandler(async (user, context, req, res) => {
- // ensure the async dependencies are loaded
- if (!context.reporting) {
- return handleUnavailable(res);
- }
-
- const { docId } = req.params;
- const {
- management: { jobTypes = [] },
- } = await reporting.getLicenseInfo();
-
- const jobsQuery = jobsQueryFactory(reporting);
- const result = await jobsQuery.getContent(user, docId);
-
- if (!result) {
- throw Boom.notFound();
- }
-
- const { jobtype: jobType, output } = result;
-
- if (!jobTypes.includes(jobType)) {
- throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`);
- }
-
- return res.ok({
- body: output?.content ?? {},
- headers: {
- 'content-type': 'application/json',
- },
- });
- })
- );
-
// return some info about the job
router.get(
{
diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts
index 76896a7472d59..a0d6962074a70 100644
--- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts
+++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts
@@ -12,7 +12,8 @@ import { i18n } from '@kbn/i18n';
import { UnwrapPromise } from '@kbn/utility-types';
import { ElasticsearchClient } from 'src/core/server';
import { ReportingCore } from '../../';
-import { ReportApiJSON, ReportDocument, ReportSource } from '../../../common/types';
+import { JobContent, ReportApiJSON, ReportDocument, ReportSource } from '../../../common/types';
+import { statuses } from '../../lib/statuses';
import { Report } from '../../lib/store';
import { ReportingUser } from '../../types';
@@ -47,6 +48,7 @@ interface JobsQueryFactory {
count(jobTypes: string[], user: ReportingUser): Promise;
get(user: ReportingUser, id: string): Promise;
getContent(user: ReportingUser, id: string): Promise;
+ getError(user: ReportingUser, id: string): Promise<(ReportContent & JobContent) | void>;
delete(deleteIndex: string, id: string): Promise>;
}
@@ -205,6 +207,20 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory
};
},
+ async getError(user, id) {
+ const content = await this.getContent(user, id);
+ if (content && content?.output?.content) {
+ if (content.status !== statuses.JOB_STATUS_FAILED) {
+ throw new Error(`Can not get error for ${id}`);
+ }
+ return {
+ ...content,
+ content: content.output.content,
+ content_type: false,
+ };
+ }
+ },
+
async delete(deleteIndex, id) {
try {
const { asInternalUser: elasticsearchClient } = await reportingCore.getEsClient();
diff --git a/x-pack/plugins/rule_registry/server/mocks.ts b/x-pack/plugins/rule_registry/server/mocks.ts
new file mode 100644
index 0000000000000..cc5c3cfd484a7
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/mocks.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 { createLifecycleAlertServicesMock } from './utils/lifecycle_alert_services_mock';
+
+export const ruleRegistryMocks = {
+ createLifecycleAlertServices: createLifecycleAlertServicesMock,
+};
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts
new file mode 100644
index 0000000000000..c519674569a51
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.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 {
+ AlertTypeParams,
+ AlertTypeState,
+ AlertInstanceState,
+ AlertInstanceContext,
+} from '../../../../plugins/alerting/server';
+import { AlertExecutorOptionsWithExtraServices } from '../types';
+
+import { LifecycleAlertServices, LifecycleRuleExecutor } from './create_lifecycle_executor';
+
+export const createLifecycleRuleExecutorMock = <
+ Params extends AlertTypeParams = never,
+ State extends AlertTypeState = never,
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+>(
+ executor: LifecycleRuleExecutor
+) => async (
+ options: AlertExecutorOptionsWithExtraServices<
+ Params,
+ State,
+ InstanceState,
+ InstanceContext,
+ ActionGroupIds,
+ LifecycleAlertServices
+ >
+) => await executor(options);
diff --git a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts
new file mode 100644
index 0000000000000..37b4847bc9c69
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.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 { AlertInstanceContext, AlertInstanceState } from '../../../alerting/server';
+import { alertsMock } from '../../../alerting/server/mocks';
+import { LifecycleAlertServices } from './create_lifecycle_executor';
+
+/**
+ * This wraps the alerts to enable the preservation of the generic type
+ * arguments of the factory function.
+ **/
+class AlertsMockWrapper<
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+> {
+ createAlertServices() {
+ return alertsMock.createAlertServices();
+ }
+}
+
+type AlertServices<
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+> = ReturnType['createAlertServices']>;
+
+export const createLifecycleAlertServicesMock = <
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+>(
+ alertServices: AlertServices
+): LifecycleAlertServices => ({
+ alertWithLifecycle: ({ id }) => alertServices.alertInstanceFactory(id),
+});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
index a89ddf3e0b250..5e851cecbd86b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
@@ -44,7 +44,7 @@ describe('timeline data providers', () => {
closeTimeline();
});
- it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
+ it.skip('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
dragAndDropFirstHostToTimeline();
openTimelineUsingToggle();
cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_DROPPED_DATA_PROVIDERS}`)
@@ -78,7 +78,7 @@ describe('timeline data providers', () => {
});
});
- it('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
+ it.skip('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
dragFirstHostToTimeline();
cy.get(IS_DRAGGING_DATA_PROVIDERS)
@@ -87,7 +87,7 @@ describe('timeline data providers', () => {
.should('have.class', 'drop-target-data-providers');
});
- it('render an extra highlighted area in dataProvider when the user starts dragging a host AND is hovering over the data providers', () => {
+ it.skip('render an extra highlighted area in dataProvider when the user starts dragging a host AND is hovering over the data providers', () => {
dragFirstHostToEmptyTimelineDataProviders();
cy.get(IS_DRAGGING_DATA_PROVIDERS)
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts
index 38c6f41f1049c..ac34d65f0fd0a 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts
@@ -79,7 +79,7 @@ describe('timeline flyout button', () => {
closeTimelineUsingCloseButton();
});
- it('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
+ it.skip('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
dragFirstHostToTimeline();
cy.get(IS_DRAGGING_DATA_PROVIDERS)
diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts
index 615421b1ef323..cf1bac421b447 100644
--- a/x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/hosts/all_hosts.ts
@@ -7,6 +7,6 @@
export const ALL_HOSTS_TABLE = '[data-test-subj="table-allHosts-loading-false"]';
-export const HOSTS_NAMES = '[data-test-subj="draggable-content-host.name"] a.euiLink';
+export const HOSTS_NAMES = '[data-test-subj="render-content-host.name"] a.euiLink';
-export const HOSTS_NAMES_DRAGGABLE = '[data-test-subj="draggable-content-host.name"]';
+export const HOSTS_NAMES_DRAGGABLE = '[data-test-subj="render-content-host.name"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts
index 536379654862c..f2a712f868850 100644
--- a/x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/hosts/uncommon_processes.ts
@@ -5,6 +5,6 @@
* 2.0.
*/
-export const PROCESS_NAME_FIELD = '[data-test-subj="draggable-content-process.name"]';
+export const PROCESS_NAME_FIELD = '[data-test-subj="render-content-process.name"]';
export const UNCOMMON_PROCESSES_TABLE = '[data-test-subj="table-uncommonProcesses-loading-false"]';
diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx
index 6d87b5d3a68b9..ad15f0a5fa9fb 100644
--- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx
@@ -340,13 +340,9 @@ describe.each(chartDataSets)('BarChart with stackByField', () => {
const dataProviderId = `draggableId.content.draggable-legend-item-uuid_v4()-${escapeDataProviderId(
stackByField
)}-${escapeDataProviderId(datum.key)}`;
-
- expect(
- wrapper
- .find(`[draggableId="${dataProviderId}"] [data-test-subj="providerContainer"]`)
- .first()
- .text()
- ).toEqual(datum.key);
+ expect(wrapper.find(`div[data-provider-id="${dataProviderId}"]`).first().text()).toEqual(
+ datum.key
+ );
});
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx
index 6017501f87dcc..493ce4da78eba 100644
--- a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx
@@ -46,6 +46,7 @@ const DraggableLegendItemComponent: React.FC<{
data-test-subj={`legend-item-${dataProviderId}`}
field={field}
id={dataProviderId}
+ isDraggable={false}
timelineId={timelineId}
value={value}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap
index aa8214938c2b0..0b25ff2c8c5ee 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap
@@ -17,6 +17,7 @@ exports[`DraggableWrapper rendering it renders against the snapshot 1`] = `
},
}
}
+ isDraggable={true}
render={[Function]}
/>
`;
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 bdc5545880e1c..d27ad96ff3c4f 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
@@ -42,7 +42,11 @@ describe('DraggableWrapper', () => {
const wrapper = shallow(
- message} />
+ message}
+ />
);
@@ -54,7 +58,11 @@ describe('DraggableWrapper', () => {
const wrapper = mount(
- message} />
+ message}
+ />
);
@@ -66,19 +74,27 @@ describe('DraggableWrapper', () => {
const wrapper = mount(
- message} />
+ message}
+ />
);
- expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(false);
+ expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(false);
});
test('it renders hover actions when the mouse is over the text of draggable wrapper', async () => {
const wrapper = mount(
- message} />
+ message}
+ />
);
@@ -88,7 +104,7 @@ describe('DraggableWrapper', () => {
wrapper.update();
jest.runAllTimers();
wrapper.update();
- expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(true);
});
});
});
@@ -98,7 +114,12 @@ describe('DraggableWrapper', () => {
const wrapper = mount(
- message} truncate />
+ message}
+ truncate
+ />
);
@@ -112,7 +133,11 @@ describe('DraggableWrapper', () => {
const wrapper = mount(
- message} />
+ message}
+ />
);
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 9db5b3899d8bc..d008aad213392 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
@@ -7,7 +7,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 React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
Draggable,
DraggableProvided,
@@ -25,12 +25,13 @@ import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/com
import { TruncatableText } from '../truncatable_text';
import { WithHoverActions } from '../with_hover_actions';
-import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content';
+
import { getDraggableId, getDroppableId } from './helpers';
import { ProviderContainer } from './provider_container';
import * as i18n from './translations';
import { useKibana } from '../../lib/kibana';
+import { useHoverActions } from '../hover_actions/use_hover_actions';
// As right now, we do not know what we want there, we will keep it as a placeholder
export const DragEffects = styled.div``;
@@ -80,7 +81,7 @@ const Wrapper = styled.div`
Wrapper.displayName = 'Wrapper';
-const ProviderContentWrapper = styled.span`
+export const ProviderContentWrapper = styled.span`
> span.euiToolTipAnchor {
display: block; /* allow EuiTooltip content to be truncatable */
}
@@ -95,6 +96,7 @@ type RenderFunctionProp = (
interface Props {
dataProvider: DataProvider;
disabled?: boolean;
+ isDraggable?: boolean;
inline?: boolean;
render: RenderFunctionProp;
timelineId?: string;
@@ -121,55 +123,35 @@ export const getStyle = (
};
};
-const draggableContainsLinks = (draggableElement: HTMLDivElement | null) => {
- const links = draggableElement?.querySelectorAll('.euiLink') ?? [];
- return links.length > 0;
-};
-
-const DraggableWrapperComponent: React.FC = ({
+const DraggableOnWrapperComponent: React.FC = ({
dataProvider,
onFilterAdded,
render,
timelineId,
truncate,
}) => {
- const keyboardHandlerRef = useRef(null);
- const draggableRef = useRef(null);
- const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
- const [showTopN, setShowTopN] = useState(false);
- const [goGetTimelineId, setGoGetTimelineId] = useState(false);
- const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
const [providerRegistered, setProviderRegistered] = useState(false);
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);
- setHoverActionsOwnFocus((prevHoverActionsOwnFocus) => {
- if (prevHoverActionsOwnFocus) {
- setTimeout(() => {
- keyboardHandlerRef.current?.focus();
- }, 0);
- }
- return false; // always give up ownership
- });
-
- setTimeout(() => {
- setHoverActionsOwnFocus(false);
- }, 0); // invoked on the next tick, because we want to restore focus first
- }, [keyboardHandlerRef]);
-
- const toggleTopN = useCallback(() => {
- setShowTopN((prevShowTopN) => {
- const newShowTopN = !prevShowTopN;
- if (newShowTopN === false) {
- handleClosePopOverTrigger();
- }
- return newShowTopN;
- });
- }, [handleClosePopOverTrigger]);
+ const {
+ closePopOverTrigger,
+ handleClosePopOverTrigger,
+ hoverActionsOwnFocus,
+ hoverContent,
+ keyboardHandlerRef,
+ onCloseRequested,
+ openPopover,
+ onFocus,
+ setContainerRef,
+ showTopN,
+ } = useHoverActions({
+ dataProvider,
+ onFilterAdded,
+ render,
+ timelineId,
+ truncate,
+ });
const registerProvider = useCallback(() => {
if (!isDisabled) {
@@ -192,49 +174,6 @@ const DraggableWrapperComponent: React.FC = ({
[unRegisterProvider]
);
- const hoverContent = useMemo(() => {
- // display links as additional content in the hover menu to enable keyboard
- // navigation of links (when the draggable contains them):
- const additionalContent =
- hoverActionsOwnFocus && !showTopN && draggableContainsLinks(draggableRef.current) ? (
-
- {render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
-
- ) : null;
-
- return (
-
- );
- }, [
- dataProvider,
- handleClosePopOverTrigger,
- hoverActionsOwnFocus,
- onFilterAdded,
- render,
- showTopN,
- timelineId,
- timelineIdFind,
- toggleTopN,
- ]);
-
const RenderClone = useCallback(
(provided, snapshot) => (
@@ -264,7 +203,7 @@ const DraggableWrapperComponent: React.FC = ({
{...provided.dragHandleProps}
ref={(e: HTMLDivElement) => {
provided.innerRef(e);
- draggableRef.current = e;
+ setContainerRef(e);
}}
data-test-subj="providerContainer"
isDragging={snapshot.isDragging}
@@ -292,13 +231,9 @@ const DraggableWrapperComponent: React.FC = ({
)}
),
- [dataProvider, registerProvider, render, truncate]
+ [dataProvider, registerProvider, render, setContainerRef, truncate]
);
- const openPopover = useCallback(() => {
- setHoverActionsOwnFocus(true);
- }, []);
-
const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
closePopover: handleClosePopOverTrigger,
draggableId: getDraggableId(dataProvider.id),
@@ -307,24 +242,6 @@ const DraggableWrapperComponent: React.FC = ({
openPopover,
});
- const onFocus = useCallback(() => {
- if (!hoverActionsOwnFocus) {
- keyboardHandlerRef.current?.focus();
- }
- }, [hoverActionsOwnFocus, keyboardHandlerRef]);
-
- const onCloseRequested = useCallback(() => {
- setShowTopN(false);
-
- if (hoverActionsOwnFocus) {
- setHoverActionsOwnFocus(false);
-
- setTimeout(() => {
- onFocus(); // return focus to this draggable on the next tick, because we owned focus
- }, 0);
- }
- }, [onFocus, hoverActionsOwnFocus]);
-
const DroppableContent = useCallback(
(droppableProvided) => (
@@ -350,7 +267,7 @@ const DraggableWrapperComponent: React.FC
= ({
{droppableProvided.placeholder}
),
- [DraggableContent, dataProvider.id, isDisabled, onBlur, onFocus, onKeyDown]
+ [DraggableContent, dataProvider.id, isDisabled, keyboardHandlerRef, onBlur, onFocus, onKeyDown]
);
const content = useMemo(
@@ -385,6 +302,75 @@ const DraggableWrapperComponent: React.FC = ({
);
};
+const DraggableWrapperComponent: React.FC = ({
+ dataProvider,
+ isDraggable = false,
+ onFilterAdded,
+ render,
+ timelineId,
+ truncate,
+}) => {
+ const {
+ closePopOverTrigger,
+ hoverActionsOwnFocus,
+ hoverContent,
+ onCloseRequested,
+ setContainerRef,
+ showTopN,
+ } = useHoverActions({
+ dataProvider,
+ isDraggable,
+ onFilterAdded,
+ render,
+ timelineId,
+ truncate,
+ });
+ const renderContent = useCallback(
+ () => (
+ {
+ setContainerRef(e);
+ }}
+ tabIndex={-1}
+ data-provider-id={getDraggableId(dataProvider.id)}
+ >
+ {truncate ? (
+
+ {render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
+
+ ) : (
+
+ {render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
+
+ )}
+
+ ),
+ [dataProvider, render, setContainerRef, truncate]
+ );
+ if (!isDraggable) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+};
+
export const DraggableWrapper = React.memo(DraggableWrapperComponent);
DraggableWrapper.displayName = 'DraggableWrapper';
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
deleted file mode 100644
index 2531780ec4bd5..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
+++ /dev/null
@@ -1,564 +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 { waitFor } from '@testing-library/react';
-import { mount, ReactWrapper } from 'enzyme';
-
-import { coreMock } from '../../../../../../../src/core/public/mocks';
-import { mockBrowserFields } from '../../containers/source/mock';
-import '../../mock/match_media';
-import { useKibana } from '../../lib/kibana';
-import { TestProviders } from '../../mock';
-import { FilterManager } from '../../../../../../../src/plugins/data/public';
-import { useSourcererScope } from '../../containers/sourcerer';
-import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
-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');
-
- return {
- ...original,
- useSourcererScope: jest.fn(),
- };
-});
-
-jest.mock('uuid', () => {
- return {
- v1: jest.fn(() => 'uuid.v1()'),
- v4: jest.fn(() => 'uuid.v4()'),
- };
-});
-const mockStartDragToTimeline = jest.fn();
-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();
-jest.mock('../../../common/hooks/use_selector', () => ({
- useShallowEqualSelector: jest.fn(),
- useDeepEqualSelector: jest.fn(),
-}));
-jest.mock('../../../common/hooks/use_invalid_filter_query.tsx');
-
-const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
-const timelineId = TimelineId.active;
-const field = 'process.name';
-const value = 'nice';
-const toggleTopN = jest.fn();
-const goGetTimelineId = jest.fn();
-const defaultProps = {
- field,
- goGetTimelineId,
- ownFocus: false,
- showTopN: false,
- timelineId,
- toggleTopN,
- value,
-};
-
-describe('DraggableWrapperHoverContent', () => {
- beforeAll(() => {
- mockStartDragToTimeline.mockReset();
- (useDeepEqualSelector as jest.Mock).mockReturnValue({
- filterManager: { addFilters: mockAddFilters },
- });
- (useSourcererScope as jest.Mock).mockReturnValue({
- browserFields: mockBrowserFields,
- selectedPatterns: [],
- indexPattern: {},
- });
- });
-
- /**
- * The tests for "Filter for value" and "Filter out value" are similar enough
- * to combine them into "table tests" using this array
- */
- const forOrOut = ['for', 'out'];
-
- forOrOut.forEach((hoverAction) => {
- describe(`Filter ${hoverAction} value`, () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
- test(`it renders the 'Filter ${hoverAction} value' button when showTopN is false`, () => {
- const wrapper = mount(
-
-
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists()
- ).toBe(true);
- });
-
- test(`it does NOT render the 'Filter ${hoverAction} value' button when showTopN is true`, () => {
- const wrapper = mount(
-
-
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists()
- ).toBe(false);
- });
-
- test(`it should call goGetTimelineId when user is over the 'Filter ${hoverAction} value' button`, () => {
- const wrapper = mount(
-
-
-
- );
- const button = wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first();
- button.simulate('mouseenter');
- expect(goGetTimelineId).toHaveBeenCalledWith(true);
- });
-
- describe('when run in the context of a timeline', () => {
- let wrapper: ReactWrapper;
- let onFilterAdded: () => void;
-
- beforeEach(() => {
- onFilterAdded = jest.fn();
-
- wrapper = mount(
-
-
-
- );
- });
-
- test('when clicked, it adds a filter to the timeline when running in the context of a timeline', () => {
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(mockAddFilters).toBeCalledWith({
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: hoverAction === 'out' ? true : false,
- params: { query: 'nice' },
- type: 'phrase',
- value: 'nice',
- },
- query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } },
- });
- });
-
- test('when clicked, invokes onFilterAdded when running in the context of a timeline', () => {
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(onFilterAdded).toBeCalled();
- });
- });
-
- describe('when NOT run in the context of a timeline', () => {
- let wrapper: ReactWrapper;
- let onFilterAdded: () => void;
- const kibana = useKibana();
-
- beforeEach(() => {
- kibana.services.data.query.filterManager.addFilters = jest.fn();
- onFilterAdded = jest.fn();
-
- wrapper = mount(
-
-
-
- );
- });
-
- test('when clicked, it adds a filter to the global filters when NOT running in the context of a timeline', () => {
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith({
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: hoverAction === 'out' ? true : false,
- params: { query: 'nice' },
- type: 'phrase',
- value: 'nice',
- },
- query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } },
- });
- });
-
- test('when clicked, invokes onFilterAdded when NOT running in the context of a timeline', () => {
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(onFilterAdded).toBeCalled();
- });
- });
-
- describe('an empty string value when run in the context of a timeline', () => {
- let filterManager: FilterManager;
- let wrapper: ReactWrapper;
- let onFilterAdded: () => void;
-
- beforeEach(() => {
- filterManager = new FilterManager(mockUiSettingsForFilterManager);
- filterManager.addFilters = jest.fn();
- onFilterAdded = jest.fn();
-
- wrapper = mount(
-
-
-
- );
- });
-
- const expectedFilterTypeDescription =
- hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"';
- test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the timeline when run in the context of a timeline`, () => {
- const expected =
- hoverAction === 'for'
- ? {
- exists: { field: 'process.name' },
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: true,
- type: 'exists',
- value: 'exists',
- },
- }
- : {
- exists: { field: 'process.name' },
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: false,
- type: 'exists',
- value: 'exists',
- },
- };
-
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(mockAddFilters).toBeCalledWith(expected);
- });
- });
-
- describe('an empty string value when NOT run in the context of a timeline', () => {
- let wrapper: ReactWrapper;
- let onFilterAdded: () => void;
- const kibana = useKibana();
-
- beforeEach(() => {
- kibana.services.data.query.filterManager.addFilters = jest.fn();
- onFilterAdded = jest.fn();
-
- wrapper = mount(
-
-
-
- );
- });
-
- const expectedFilterTypeDescription =
- hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"';
- test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the global filters when NOT running in the context of a timeline`, () => {
- const expected =
- hoverAction === 'for'
- ? {
- exists: { field: 'process.name' },
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: true,
- type: 'exists',
- value: 'exists',
- },
- }
- : {
- exists: { field: 'process.name' },
- meta: {
- alias: null,
- disabled: false,
- key: 'process.name',
- negate: false,
- type: 'exists',
- value: 'exists',
- },
- };
-
- wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
- wrapper.update();
-
- expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith(expected);
- });
- });
- });
- });
-
- describe('Add to timeline', () => {
- const aggregatableStringField = 'cloud.account.id';
- const draggableId = 'draggable.id';
-
- [false, true].forEach((showTopN) => {
- [value, null].forEach((maybeValue) => {
- [draggableId, undefined].forEach((maybeDraggableId) => {
- const shouldRender = !showTopN && maybeValue != null && maybeDraggableId != null;
- const assertion = shouldRender ? 'should render' : 'should NOT render';
-
- test(`it ${assertion} the 'Add to timeline investigation' button when showTopN is ${showTopN}, value is ${maybeValue}, and a draggableId is ${maybeDraggableId}`, () => {
- const wrapper = mount(
-
-
-
- );
-
- expect(wrapper.find('[data-test-subj="add-to-timeline"]').first().exists()).toBe(
- shouldRender
- );
- });
- });
- });
- });
-
- test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', async () => {
- const wrapper = mount(
-
-
-
- );
-
- wrapper.find('[data-test-subj="add-to-timeline"]').first().simulate('click');
-
- await waitFor(() => {
- wrapper.update();
- expect(mockStartDragToTimeline).toHaveBeenCalled();
- });
- });
- });
-
- describe('Top N', () => {
- test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, () => {
- const aggregatableStringField = 'cloud.account.id';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true);
- });
-
- test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true);
- });
-
- test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, () => {
- const notKnownToBrowserFields = 'unknown.field';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false);
- });
-
- test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, async () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
- const button = wrapper.find(`[data-test-subj="show-top-field"]`).first();
- button.simulate('mouseenter');
- await waitFor(() => {
- expect(goGetTimelineId).toHaveBeenCalledWith(true);
- });
- });
-
- test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- wrapper.find('[data-test-subj="show-top-field"]').first().simulate('click');
- wrapper.update();
-
- expect(toggleTopN).toBeCalled();
- });
-
- test(`it does NOT render the Top N histogram when when showTopN is false`, () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(wrapper.find('[data-test-subj="eventsByDatasetOverviewPanel"]').first().exists()).toBe(
- false
- );
- });
-
- test(`it does NOT render the 'Show top field' button when showTopN is true`, () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false);
- });
-
- test(`it renders the Top N histogram when when showTopN is true`, () => {
- const allowlistedField = 'signal.rule.name';
- const wrapper = mount(
-
-
-
- );
-
- wrapper.update();
-
- expect(
- wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').first().exists()
- ).toBe(true);
- });
- });
-
- describe('Copy to Clipboard', () => {
- test(`it renders the 'Copy to Clipboard' button when showTopN is false`, () => {
- const wrapper = mount(
-
-
-
- );
-
- expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(true);
- });
-
- test(`it does NOT render the 'Copy to Clipboard' button when showTopN is true`, () => {
- const wrapper = mount(
-
-
-
- );
-
- expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(false);
- });
- });
-});
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
deleted file mode 100644
index 71c3114015a03..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
+++ /dev/null
@@ -1,425 +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 {
- EuiButtonIcon,
- EuiFocusTrap,
- EuiPanel,
- 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 { getAllFieldsByName } from '../../containers/source';
-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';
-import { createFilter } from '../add_filter_to_global_search_bar';
-import { StatefulTopN } from '../top_n';
-
-import { allowTopN } from './helpers';
-import * as i18n from './translations';
-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;
-`;
-
-AdditionalContent.displayName = 'AdditionalContent';
-
-const getAdditionalScreenReaderOnlyContext = ({
- field,
- value,
-}: {
- field: string;
- value?: string[] | string | null;
-}): string => {
- if (value == null) {
- return field;
- }
-
- return Array.isArray(value) ? `${field} ${value.join(' ')}` : `${field} ${value}`;
-};
-
-const FILTER_FOR_VALUE_KEYBOARD_SHORTCUT = 'f';
-const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'o';
-const ADD_TO_TIMELINE_KEYBOARD_SHORTCUT = 'a';
-const SHOW_TOP_N_KEYBOARD_SHORTCUT = 't';
-const COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT = 'c';
-
-interface Props {
- additionalContent?: React.ReactNode;
- closePopOver?: () => void;
- draggableId?: DraggableId;
- field: string;
- goGetTimelineId?: (args: boolean) => void;
- onFilterAdded?: () => void;
- ownFocus: boolean;
- showTopN: boolean;
- timelineId?: string | null;
- toggleTopN: () => void;
- value?: string[] | string | null;
-}
-
-/** Returns a value for the `disabled` prop of `EuiFocusTrap` */
-const isFocusTrapDisabled = ({
- ownFocus,
- showTopN,
-}: {
- ownFocus: boolean;
- showTopN: boolean;
-}): boolean => {
- if (showTopN) {
- return false; // we *always* want to trap focus when showing Top N
- }
-
- return !ownFocus;
-};
-
-const DraggableWrapperHoverContentComponent: React.FC = ({
- additionalContent = null,
- closePopOver,
- draggableId,
- field,
- goGetTimelineId,
- onFilterAdded,
- ownFocus,
- showTopN,
- timelineId,
- toggleTopN,
- value,
-}) => {
- 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 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 ? activeFilterMananager : filterManagerBackup),
- [timelineId, activeFilterMananager, filterManagerBackup]
- );
-
- // Regarding data from useManageTimeline:
- // * `indexToAdd`, which enables the alerts index to be appended to
- // the `indexPattern` returned by `useWithSource`, may only be populated when
- // this component is rendered in the context of the active timeline. This
- // behavior enables the 'All events' view by appending the alerts index
- // to the index pattern.
- const activeScope: SourcererScopeName =
- timelineId === TimelineId.active
- ? SourcererScopeName.timeline
- : timelineId != null &&
- [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
- timelineId as TimelineId
- )
- ? SourcererScopeName.detections
- : SourcererScopeName.default;
- const { browserFields, indexPattern } = useSourcererScope(activeScope);
- const handleStartDragToTimeline = useCallback(() => {
- startDragToTimeline();
- if (closePopOver != null) {
- closePopOver();
- }
- }, [closePopOver, startDragToTimeline]);
-
- const filterForValue = useCallback(() => {
- const filter =
- value?.length === 0 ? createFilter(field, undefined) : createFilter(field, value);
- const activeFilterManager = filterManager;
-
- if (activeFilterManager != null) {
- activeFilterManager.addFilters(filter);
- if (closePopOver != null) {
- closePopOver();
- }
- if (onFilterAdded != null) {
- onFilterAdded();
- }
- }
- }, [closePopOver, field, value, filterManager, onFilterAdded]);
-
- const filterOutValue = useCallback(() => {
- const filter =
- value?.length === 0 ? createFilter(field, null, false) : createFilter(field, value, true);
- const activeFilterManager = filterManager;
-
- if (activeFilterManager != null) {
- activeFilterManager.addFilters(filter);
-
- if (closePopOver != null) {
- closePopOver();
- }
- if (onFilterAdded != null) {
- onFilterAdded();
- }
- }
- }, [closePopOver, field, value, filterManager, onFilterAdded]);
-
- const isInit = useRef(true);
-
- useEffect(() => {
- if (isInit.current && goGetTimelineId != null && timelineId == null) {
- isInit.current = false;
- goGetTimelineId(true);
- }
- }, [goGetTimelineId, timelineId]);
-
- useEffect(() => {
- if (ownFocus) {
- setTimeout(() => {
- defaultFocusedButtonRef.current?.focus();
- }, 0);
- }
- }, [ownFocus]);
-
- const onKeyDown = useCallback(
- (keyboardEvent: React.KeyboardEvent) => {
- if (!ownFocus) {
- return;
- }
-
- switch (keyboardEvent.key) {
- case FILTER_FOR_VALUE_KEYBOARD_SHORTCUT:
- stopPropagationAndPreventDefault(keyboardEvent);
- filterForValue();
- break;
- case FILTER_OUT_VALUE_KEYBOARD_SHORTCUT:
- stopPropagationAndPreventDefault(keyboardEvent);
- filterOutValue();
- break;
- case ADD_TO_TIMELINE_KEYBOARD_SHORTCUT:
- stopPropagationAndPreventDefault(keyboardEvent);
- handleStartDragToTimeline();
- break;
- case SHOW_TOP_N_KEYBOARD_SHORTCUT:
- stopPropagationAndPreventDefault(keyboardEvent);
- toggleTopN();
- break;
- case COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT:
- stopPropagationAndPreventDefault(keyboardEvent);
- const copyToClipboardButton = panelRef.current?.querySelector(
- `.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
- );
- if (copyToClipboardButton != null) {
- copyToClipboardButton.click();
- if (closePopOver != null) {
- closePopOver();
- }
- }
- break;
- case 'Enter':
- break;
- case 'Escape':
- stopPropagationAndPreventDefault(keyboardEvent);
- if (closePopOver != null) {
- closePopOver();
- }
- break;
- default:
- break;
- }
- },
-
- [closePopOver, filterForValue, filterOutValue, handleStartDragToTimeline, ownFocus, toggleTopN]
- );
-
- return (
-
-
-
- {i18n.YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(field)}
-
-
- {additionalContent != null && {additionalContent} }
-
- {!showTopN && value != null && (
-
- }
- >
-
-
- )}
-
- {!showTopN && value != null && (
-
- }
- >
-
-
- )}
-
- {!showTopN && value != null && draggableId != null && (
-
- }
- >
-
-
- )}
-
- <>
- {allowTopN({
- browserField: getAllFieldsByName(browserFields)[field],
- fieldName: field,
- }) && (
- <>
- {!showTopN && (
-
- }
- >
-
-
- )}
-
- {showTopN && (
-
- )}
- >
- )}
- >
-
- {!showTopN && (
-
- )}
-
-
- );
-};
-
-DraggableWrapperHoverContentComponent.displayName = 'DraggableWrapperHoverContentComponent';
-
-export const DraggableWrapperHoverContent = React.memo(DraggableWrapperHoverContentComponent);
-
-export const useGetTimelineId = function (
- elem: React.MutableRefObject,
- getTimelineId: boolean = false
-) {
- const [timelineId, setTimelineId] = useState(null);
-
- useEffect(() => {
- let startElem: Element | (Node & ParentNode) | null = elem.current;
- if (startElem != null && getTimelineId) {
- for (; startElem && startElem !== document; startElem = startElem.parentNode) {
- const myElem: Element = startElem as Element;
- if (
- myElem != null &&
- myElem.classList != null &&
- myElem.classList.contains(SELECTOR_TIMELINE_GLOBAL_CONTAINER) &&
- myElem.hasAttribute('data-timeline-id')
- ) {
- setTimelineId(myElem.getAttribute('data-timeline-id'));
- break;
- }
- }
- }
- }, [elem, getTimelineId]);
-
- return timelineId;
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/use_get_timeline_id_from_dom.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/use_get_timeline_id_from_dom.tsx
new file mode 100644
index 0000000000000..fcb547842aec4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/use_get_timeline_id_from_dom.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, { useEffect, useState } from 'react';
+
+import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
+
+export const useGetTimelineId = function (
+ elem: React.MutableRefObject,
+ getTimelineId: boolean = false
+) {
+ const [timelineId, setTimelineId] = useState(null);
+
+ useEffect(() => {
+ let startElem: Element | (Node & ParentNode) | null = elem.current;
+ if (startElem != null && getTimelineId) {
+ for (; startElem && startElem !== document; startElem = startElem.parentNode) {
+ const myElem: Element = startElem as Element;
+ if (
+ myElem != null &&
+ myElem.classList != null &&
+ myElem.classList.contains(SELECTOR_TIMELINE_GLOBAL_CONTAINER) &&
+ myElem.hasAttribute('data-timeline-id')
+ ) {
+ setTimelineId(myElem.getAttribute('data-timeline-id'));
+ break;
+ }
+ }
+ }
+ }, [elem, getTimelineId]);
+
+ return timelineId;
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap
index 93608a181adff..6b27cf5969f1a 100644
--- a/x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/draggables/__snapshots__/index.test.tsx.snap
@@ -36,6 +36,7 @@ exports[`draggables rendering it renders the default DefaultDraggable 1`] = `
},
}
}
+ isDraggable={true}
render={[Function]}
/>
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
index df92da0c7d056..6ac1746d77709 100644
--- a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
@@ -20,6 +20,7 @@ import { Provider } from '../../../timelines/components/timeline/data_providers/
export interface DefaultDraggableType {
id: string;
+ isDraggable?: boolean;
field: string;
value?: string | null;
name?: string | null;
@@ -79,6 +80,7 @@ Content.displayName = 'Content';
* that's only displayed when the specified value is non-`null`.
*
* @param id - a unique draggable id, which typically follows the format `${contextId}-${eventId}-${field}-${value}`
+ * @param isDraggable - optional prop to disable drag & drop and it will defaulted to true
* @param field - the name of the field, e.g. `network.transport`
* @param value - value of the field e.g. `tcp`
* @param name - defaulting to `field`, this optional human readable name is used by the `DataProvider` that represents the data
@@ -88,7 +90,17 @@ Content.displayName = 'Content';
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
*/
export const DefaultDraggable = React.memo(
- ({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) => {
+ ({
+ id,
+ isDraggable = true,
+ field,
+ value,
+ name,
+ children,
+ timelineId,
+ tooltipContent,
+ queryValue,
+ }) => {
const dataProviderProp: DataProvider = useMemo(
() => ({
and: [],
@@ -125,6 +137,7 @@ export const DefaultDraggable = React.memo(
return (
@@ -155,6 +168,7 @@ export type BadgeDraggableType = Omit & {
* @param field - the name of the field, e.g. `network.transport`
* @param value - value of the field e.g. `tcp`
* @param iconType -the (optional) type of icon e.g. `snowflake` to display on the badge
+ * @param isDraggable
* @param name - defaulting to `field`, this optional human readable name is used by the `DataProvider` that represents the data
* @param color - defaults to `hollow`, optionally overwrite the color of the badge icon
* @param children - defaults to displaying `value`, this allows an arbitrary visualization to be displayed in lieu of the default behavior
@@ -168,6 +182,7 @@ const DraggableBadgeComponent: React.FC = ({
field,
value,
iconType,
+ isDraggable,
name,
color = 'hollow',
children,
@@ -177,6 +192,7 @@ const DraggableBadgeComponent: React.FC = ({
value != null ? (
= ({
key={key}
contextId={key}
eventId={eventId}
+ isDraggable={false}
fieldName={fieldName || 'unknown'}
value={value}
/>
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 80c014771ae68..28a90e94c0ca4 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
@@ -245,7 +245,7 @@ describe('EventFieldsBrowser', () => {
/>
);
- expect(wrapper.find('[data-test-subj="draggable-content-@timestamp"]').at(0).text()).toEqual(
+ expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').at(0).text()).toEqual(
'Feb 28, 2019 @ 16:50:54.621'
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx
index f5cf600e281ad..67b1874eea0a0 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx
@@ -6,11 +6,10 @@
*/
import React, { useCallback, useState, useRef } from 'react';
-import { getDraggableId } from '@kbn/securitysolution-t-grid';
import { HoverActions } from '../../hover_actions';
import { useActionCellDataProvider } from './use_action_cell_data_provider';
import { EventFieldsData } from '../types';
-import { useGetTimelineId } from '../../drag_and_drop/draggable_wrapper_hover_content';
+import { useGetTimelineId } from '../../drag_and_drop/use_get_timeline_id_from_dom';
import { ColumnHeaderOptions } from '../../../../../common/types/timeline';
import { BrowserField } from '../../../containers/source';
@@ -66,11 +65,10 @@ export const ActionCell: React.FC = React.memo(
});
}, []);
- const draggableIds = actionCellConfig?.idList.map((id) => getDraggableId(id));
return (
({
+ and: [],
+ enabled: true,
+ id: escapeDataProviderId(id),
+ name: field,
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field,
+ value,
+ operator: IS_OPERATOR,
+ },
+});
+
export const useActionCellDataProvider = ({
contextId,
eventId,
@@ -50,72 +66,90 @@ export const useActionCellDataProvider = ({
isObjectArray,
linkValue,
values,
-}: UseActionCellDataProvider): { idList: string[]; stringValues: string[] } | null => {
- if (values === null || values === undefined) return null;
-
- const stringifiedValues: string[] = [];
- const arrayValues = Array.isArray(values) ? values : [values];
+}: UseActionCellDataProvider): {
+ stringValues: string[];
+ dataProvider: DataProvider[];
+} | null => {
+ const cellData = useMemo(() => {
+ if (values === null || values === undefined) return null;
+ const arrayValues = Array.isArray(values) ? values : [values];
+ return arrayValues.reduce<{
+ stringValues: string[];
+ dataProvider: DataProvider[];
+ }>(
+ (memo, value, index) => {
+ let id: string = '';
+ let valueAsString: string = isString(value) ? value : `${values}`;
+ const appendedUniqueId = `${contextId}-${eventId}-${field}-${index}-${value}`;
+ if (fieldFromBrowserField == null) {
+ memo.stringValues.push(valueAsString);
+ return memo;
+ }
- const idList: string[] = arrayValues.reduce((memo, value, index) => {
- let id = null;
- let valueAsString: string = isString(value) ? value : `${values}`;
- if (fieldFromBrowserField == null) {
- stringifiedValues.push(valueAsString);
- return memo;
- }
- const appendedUniqueId = `${contextId}-${eventId}-${field}-${index}-${value}-${eventId}-${field}-${value}`;
- if (isObjectArray || fieldType === GEO_FIELD_TYPE || [MESSAGE_FIELD_NAME].includes(field)) {
- stringifiedValues.push(valueAsString);
- return memo;
- } else if (fieldType === IP_FIELD_TYPE) {
- id = `formatted-ip-data-provider-${contextId}-${field}-${value}-${eventId}`;
- if (isString(value) && !isEmpty(value)) {
- try {
- const addresses = JSON.parse(value);
- if (isArray(addresses)) {
- valueAsString = addresses.join(',');
+ if (isObjectArray || fieldType === GEO_FIELD_TYPE || [MESSAGE_FIELD_NAME].includes(field)) {
+ memo.stringValues.push(valueAsString);
+ return memo;
+ } else if (fieldType === IP_FIELD_TYPE) {
+ id = `formatted-ip-data-provider-${contextId}-${field}-${value}-${eventId}`;
+ if (isString(value) && !isEmpty(value)) {
+ try {
+ const addresses = JSON.parse(value);
+ if (isArray(addresses)) {
+ valueAsString = addresses.join(',');
+ addresses.forEach((ip) => memo.dataProvider.push(getDataProvider(field, id, ip)));
+ }
+ } catch (_) {
+ // Default to keeping the existing string value
+ }
+ memo.stringValues.push(valueAsString);
+ return memo;
}
- } catch (_) {
- // Default to keeping the existing string value
+ } else if (PORT_NAMES.some((portName) => field === portName)) {
+ id = `port-default-draggable-${appendedUniqueId}`;
+ } else if (field === EVENT_DURATION_FIELD_NAME) {
+ id = `duration-default-draggable-${appendedUniqueId}`;
+ } else if (field === HOST_NAME_FIELD_NAME) {
+ id = `event-details-value-default-draggable-${appendedUniqueId}`;
+ } else if (fieldFormat === BYTES_FORMAT) {
+ id = `bytes-default-draggable-${appendedUniqueId}`;
+ } else if (field === SIGNAL_RULE_NAME_FIELD_NAME) {
+ id = `event-details-value-default-draggable-${appendedUniqueId}-${linkValue}`;
+ } else if (field === EVENT_MODULE_FIELD_NAME) {
+ id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
+ } else if (field === SIGNAL_STATUS_FIELD_NAME) {
+ id = `alert-details-value-default-draggable-${appendedUniqueId}`;
+ } else if (field === AGENT_STATUS_FIELD_NAME) {
+ const valueToUse = typeof value === 'string' ? value : '';
+ id = `event-details-value-default-draggable-${appendedUniqueId}`;
+ valueAsString = valueToUse;
+ } else if (
+ [
+ RULE_REFERENCE_FIELD_NAME,
+ REFERENCE_URL_FIELD_NAME,
+ EVENT_URL_FIELD_NAME,
+ INDICATOR_REFERENCE,
+ ].includes(field)
+ ) {
+ id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
+ } else {
+ id = `event-details-value-default-draggable-${appendedUniqueId}`;
}
- }
- } else if (PORT_NAMES.some((portName) => field === portName)) {
- id = `port-default-draggable-${appendedUniqueId}`;
- } else if (field === EVENT_DURATION_FIELD_NAME) {
- id = `duration-default-draggable-${appendedUniqueId}`;
- } else if (field === HOST_NAME_FIELD_NAME) {
- id = `event-details-value-default-draggable-${appendedUniqueId}`;
- } else if (fieldFormat === BYTES_FORMAT) {
- id = `bytes-default-draggable-${appendedUniqueId}`;
- } else if (field === SIGNAL_RULE_NAME_FIELD_NAME) {
- id = `event-details-value-default-draggable-${appendedUniqueId}-${linkValue}`;
- } else if (field === EVENT_MODULE_FIELD_NAME) {
- id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
- } else if (field === SIGNAL_STATUS_FIELD_NAME) {
- id = `alert-details-value-default-draggable-${appendedUniqueId}`;
- } else if (field === AGENT_STATUS_FIELD_NAME) {
- const valueToUse = typeof value === 'string' ? value : '';
- id = `event-details-value-default-draggable-${appendedUniqueId}`;
- valueAsString = valueToUse;
- } else if (
- [
- RULE_REFERENCE_FIELD_NAME,
- REFERENCE_URL_FIELD_NAME,
- EVENT_URL_FIELD_NAME,
- INDICATOR_REFERENCE,
- ].includes(field)
- ) {
- id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
- } else {
- id = `event-details-value-default-draggable-${appendedUniqueId}`;
- }
- stringifiedValues.push(valueAsString);
- memo.push(escapeDataProviderId(id));
- return memo;
- }, [] as string[]);
-
- return {
- idList,
- stringValues: stringifiedValues,
- };
+ memo.stringValues.push(valueAsString);
+ memo.dataProvider.push(getDataProvider(field, id, value));
+ return memo;
+ },
+ { stringValues: [], dataProvider: [] }
+ );
+ }, [
+ contextId,
+ eventId,
+ field,
+ fieldFormat,
+ fieldFromBrowserField,
+ fieldType,
+ isObjectArray,
+ linkValue,
+ values,
+ ]);
+ return cellData;
};
diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx
index 31bdf78626e7c..a1ba33f30cc55 100644
--- a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx
@@ -6,16 +6,17 @@
*/
import { EuiFocusTrap, EuiScreenReaderOnly } from '@elastic/eui';
-import React, { useCallback, useEffect, useRef, useMemo } from 'react';
+import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react';
import { DraggableId } from 'react-beautiful-dnd';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
-import { getAllFieldsByName } from '../../containers/source';
-import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../lib/clipboard/clipboard';
+import { isEmpty } from 'lodash';
+
import { useKibana } from '../../lib/kibana';
+import { getAllFieldsByName } from '../../containers/source';
import { allowTopN } from './utils';
import { useDeepEqualSelector } from '../../hooks/use_selector';
-import { ColumnHeaderOptions, TimelineId } from '../../../../common/types/timeline';
+import { ColumnHeaderOptions, DataProvider, TimelineId } from '../../../../common/types/timeline';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { useSourcererScope } from '../../containers/sourcerer';
import { timelineSelectors } from '../../../timelines/store/timeline';
@@ -38,43 +39,51 @@ export const AdditionalContent = styled.div`
AdditionalContent.displayName = 'AdditionalContent';
-const StyledHoverActionsContainer = styled.div<{ $showTopN: boolean }>`
+const StyledHoverActionsContainer = styled.div<{ $showTopN: boolean; $showOwnFocus: boolean }>`
padding: ${(props) => `0 ${props.theme.eui.paddingSizes.s}`};
display: flex;
- &:focus-within {
- .timelines__hoverActionButton,
- .securitySolution__hoverActionButton {
- opacity: 1;
+ ${(props) =>
+ props.$showOwnFocus
+ ? `
+ &:focus-within {
+ .timelines__hoverActionButton,
+ .securitySolution__hoverActionButton {
+ opacity: 1;
+ }
}
- }
- &:hover {
- .timelines__hoverActionButton,
- .securitySolution__hoverActionButton {
- opacity: 1;
+ &:hover {
+ .timelines__hoverActionButton,
+ .securitySolution__hoverActionButton {
+ opacity: 1;
+ }
}
- }
.timelines__hoverActionButton,
.securitySolution__hoverActionButton {
- opacity: ${(props) => (props.$showTopN ? 1 : 0)};
+ opacity: ${props.$showTopN ? 1 : 0};
- &:focus {
- opacity: 1;
+ &:focus {
+ opacity: 1;
+ }
}
- }
+ `
+ : ''}
`;
interface Props {
additionalContent?: React.ReactNode;
+ closePopOver?: () => void;
+ dataProvider?: DataProvider | DataProvider[];
dataType?: string;
- draggableIds?: DraggableId[];
+ draggableId?: DraggableId;
field: string;
goGetTimelineId?: (args: boolean) => void;
isObjectArray: boolean;
onFilterAdded?: () => void;
ownFocus: boolean;
+ showOwnFocus?: boolean;
showTopN: boolean;
timelineId?: string | null;
toggleColumn?: (column: ColumnHeaderOptions) => void;
@@ -100,13 +109,15 @@ const isFocusTrapDisabled = ({
export const HoverActions: React.FC = React.memo(
({
additionalContent = null,
+ dataProvider,
dataType,
- draggableIds,
+ draggableId,
field,
goGetTimelineId,
isObjectArray,
onFilterAdded,
ownFocus,
+ showOwnFocus = true,
showTopN,
timelineId,
toggleColumn,
@@ -117,29 +128,13 @@ export const HoverActions: React.FC = React.memo(
const { timelines } = kibana.services;
// Common actions used by the alert table and alert flyout
const {
- addToTimeline: {
- AddToTimelineButton,
- keyboardShortcut: addToTimelineKeyboardShortcut,
- useGetHandleStartDragToTimeline,
- },
- columnToggle: {
- ColumnToggleButton,
- columnToggleFn,
- keyboardShortcut: columnToggleKeyboardShortcut,
- },
- copy: { CopyButton, keyboardShortcut: copyKeyboardShortcut },
- filterForValue: {
- FilterForValueButton,
- filterForValueFn,
- keyboardShortcut: filterForValueKeyboardShortcut,
- },
- filterOutValue: {
- FilterOutValueButton,
- filterOutValueFn,
- keyboardShortcut: filterOutValueKeyboardShortcut,
- },
+ getAddToTimelineButton,
+ getColumnToggleButton,
+ getCopyButton,
+ getFilterForValueButton,
+ getFilterOutValueButton,
} = timelines.getHoverActions();
-
+ const [stKeyboardEvent, setStKeyboardEvent] = useState();
const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
kibana.services.data.query.filterManager,
]);
@@ -169,30 +164,8 @@ export const HoverActions: React.FC = React.memo(
: SourcererScopeName.default;
const { browserFields } = useSourcererScope(activeScope);
- const handleStartDragToTimeline = (() => {
- const handleStartDragToTimelineFns = draggableIds?.map((draggableId) => {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- return useGetHandleStartDragToTimeline({ draggableId, field });
- });
- return () => handleStartDragToTimelineFns?.forEach((dragFn) => dragFn());
- })();
-
- const handleFilterForValue = useCallback(() => {
- filterForValueFn({ field, value: values, filterManager, onFilterAdded });
- }, [filterForValueFn, field, values, filterManager, onFilterAdded]);
-
- const handleFilterOutValue = useCallback(() => {
- filterOutValueFn({ field, value: values, filterManager, onFilterAdded });
- }, [filterOutValueFn, field, values, filterManager, onFilterAdded]);
-
- const handleToggleColumn = useCallback(
- () => (toggleColumn ? columnToggleFn({ toggleColumn, field }) : null),
- [columnToggleFn, field, toggleColumn]
- );
-
const isInit = useRef(true);
const defaultFocusedButtonRef = useRef(null);
- const panelRef = useRef(null);
useEffect(() => {
if (isInit.current && goGetTimelineId != null && timelineId == null) {
@@ -215,31 +188,6 @@ export const HoverActions: React.FC = React.memo(
return;
}
switch (keyboardEvent.key) {
- case addToTimelineKeyboardShortcut:
- stopPropagationAndPreventDefault(keyboardEvent);
- handleStartDragToTimeline();
- break;
- case columnToggleKeyboardShortcut:
- stopPropagationAndPreventDefault(keyboardEvent);
- handleToggleColumn();
- break;
- case copyKeyboardShortcut:
- stopPropagationAndPreventDefault(keyboardEvent);
- const copyToClipboardButton = panelRef.current?.querySelector(
- `.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
- );
- if (copyToClipboardButton != null) {
- copyToClipboardButton.click();
- }
- break;
- case filterForValueKeyboardShortcut:
- stopPropagationAndPreventDefault(keyboardEvent);
- handleFilterForValue();
- break;
- case filterOutValueKeyboardShortcut:
- stopPropagationAndPreventDefault(keyboardEvent);
- handleFilterOutValue();
- break;
case SHOW_TOP_N_KEYBOARD_SHORTCUT:
stopPropagationAndPreventDefault(keyboardEvent);
toggleTopN();
@@ -250,33 +198,26 @@ export const HoverActions: React.FC = React.memo(
stopPropagationAndPreventDefault(keyboardEvent);
break;
default:
+ setStKeyboardEvent(keyboardEvent);
break;
}
},
- [
- addToTimelineKeyboardShortcut,
- columnToggleKeyboardShortcut,
- copyKeyboardShortcut,
- filterForValueKeyboardShortcut,
- filterOutValueKeyboardShortcut,
- handleFilterForValue,
- handleFilterOutValue,
- handleStartDragToTimeline,
- handleToggleColumn,
- ownFocus,
- toggleTopN,
- ]
+ [ownFocus, toggleTopN]
);
const showFilters = values != null;
return (
-
-
+
{YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(field)}
@@ -286,46 +227,58 @@ export const HoverActions: React.FC = React.memo(
{showFilters && (
<>
-
-
+
+ {getFilterForValueButton({
+ defaultFocusedButtonRef,
+ field,
+ filterManager,
+ keyboardEvent: stKeyboardEvent,
+ onFilterAdded,
+ ownFocus,
+ showTooltip: true,
+ value: values,
+ })}
+
+
+ {getFilterOutValueButton({
+ field,
+ filterManager,
+ keyboardEvent: stKeyboardEvent,
+ onFilterAdded,
+ ownFocus,
+ showTooltip: true,
+ value: values,
+ })}
+
>
)}
{toggleColumn && (
-
+
+ {getColumnToggleButton({
+ field,
+ isDisabled: isObjectArray && dataType !== 'geo_point',
+ isObjectArray,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ showTooltip: true,
+ toggleColumn,
+ value: values,
+ })}
+
)}
- {showFilters && draggableIds != null && (
-
+ {showFilters && (draggableId != null || !isEmpty(dataProvider)) && (
+
+ {getAddToTimelineButton({
+ dataProvider,
+ draggableId,
+ field,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ showTooltip: true,
+ value: values,
+ })}
+
)}
{allowTopN({
browserField: getAllFieldsByName(browserFields)[field],
@@ -342,18 +295,20 @@ export const HoverActions: React.FC = React.memo(
value={values}
/>
)}
- {showFilters && (
-
+ {field != null && (
+
+ {getCopyButton({
+ field,
+ isHoverAction: true,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ showTooltip: true,
+ value: values,
+ })}
+
)}
-
-
+
+
);
}
);
diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx
new file mode 100644
index 0000000000000..373f944b70a81
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx
@@ -0,0 +1,178 @@
+/*
+ * 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, useMemo, useState, useRef } from 'react';
+import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd';
+import { HoverActions } from '.';
+
+import { DataProvider } from '../../../../common/types';
+import { ProviderContentWrapper } from '../drag_and_drop/draggable_wrapper';
+import { getDraggableId } from '../drag_and_drop/helpers';
+import { useGetTimelineId } from '../drag_and_drop/use_get_timeline_id_from_dom';
+
+const draggableContainsLinks = (draggableElement: HTMLDivElement | null) => {
+ const links = draggableElement?.querySelectorAll('.euiLink') ?? [];
+ return links.length > 0;
+};
+
+type RenderFunctionProp = (
+ props: DataProvider,
+ provided: DraggableProvided | null,
+ state: DraggableStateSnapshot
+) => React.ReactNode;
+
+interface Props {
+ dataProvider: DataProvider;
+ disabled?: boolean;
+ isDraggable?: boolean;
+ inline?: boolean;
+ render: RenderFunctionProp;
+ timelineId?: string;
+ truncate?: boolean;
+ onFilterAdded?: () => void;
+}
+
+export const useHoverActions = ({
+ dataProvider,
+ isDraggable,
+ onFilterAdded,
+ render,
+ timelineId,
+}: Props) => {
+ const containerRef = useRef(null);
+ const keyboardHandlerRef = useRef(null);
+ const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
+ const [showTopN, setShowTopN] = useState(false);
+ const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState(false);
+ const [goGetTimelineId, setGoGetTimelineId] = useState(false);
+ const timelineIdFind = useGetTimelineId(containerRef, goGetTimelineId);
+
+ const handleClosePopOverTrigger = useCallback(() => {
+ setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
+ setHoverActionsOwnFocus((prevHoverActionsOwnFocus) => {
+ if (prevHoverActionsOwnFocus) {
+ setTimeout(() => {
+ keyboardHandlerRef.current?.focus();
+ }, 0);
+ }
+ return false; // always give up ownership
+ });
+
+ setTimeout(() => {
+ setHoverActionsOwnFocus(false);
+ }, 0); // invoked on the next tick, because we want to restore focus first
+ }, [keyboardHandlerRef]);
+
+ const toggleTopN = useCallback(() => {
+ setShowTopN((prevShowTopN) => {
+ const newShowTopN = !prevShowTopN;
+ if (newShowTopN === false) {
+ handleClosePopOverTrigger();
+ }
+ return newShowTopN;
+ });
+ }, [handleClosePopOverTrigger]);
+
+ const hoverContent = useMemo(() => {
+ // display links as additional content in the hover menu to enable keyboard
+ // navigation of links (when the draggable contains them):
+ const additionalContent =
+ hoverActionsOwnFocus && !showTopN && draggableContainsLinks(containerRef.current) ? (
+
+ {render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
+
+ ) : null;
+
+ return (
+
+ );
+ }, [
+ dataProvider,
+ handleClosePopOverTrigger,
+ hoverActionsOwnFocus,
+ isDraggable,
+ onFilterAdded,
+ render,
+ showTopN,
+ timelineId,
+ timelineIdFind,
+ toggleTopN,
+ ]);
+
+ const setContainerRef = useCallback((e: HTMLDivElement) => {
+ containerRef.current = e;
+ }, []);
+
+ const onFocus = useCallback(() => {
+ if (!hoverActionsOwnFocus) {
+ keyboardHandlerRef.current?.focus();
+ }
+ }, [hoverActionsOwnFocus, keyboardHandlerRef]);
+
+ const onCloseRequested = useCallback(() => {
+ setShowTopN(false);
+
+ if (hoverActionsOwnFocus) {
+ setHoverActionsOwnFocus(false);
+
+ setTimeout(() => {
+ onFocus(); // return focus to this draggable on the next tick, because we owned focus
+ }, 0);
+ }
+ }, [onFocus, hoverActionsOwnFocus]);
+
+ const openPopover = useCallback(() => {
+ setHoverActionsOwnFocus(true);
+ }, []);
+
+ return useMemo(
+ () => ({
+ closePopOverTrigger,
+ handleClosePopOverTrigger,
+ hoverActionsOwnFocus,
+ hoverContent,
+ keyboardHandlerRef,
+ onCloseRequested,
+ onFocus,
+ openPopover,
+ setContainerRef,
+ showTopN,
+ }),
+ [
+ closePopOverTrigger,
+ handleClosePopOverTrigger,
+ hoverActionsOwnFocus,
+ hoverContent,
+ onCloseRequested,
+ onFocus,
+ openPopover,
+ setContainerRef,
+ showTopN,
+ ]
+ );
+};
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 2ecda8482e340..45883019b9ff8 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
@@ -70,67 +70,6 @@ describe('get_anomalies_host_table_columns', () => {
expect(columns.some((col) => col.name === i18n.HOST_NAME)).toEqual(false);
});
- test('on host page, we should escape the draggable id', () => {
- const columns = getAnomaliesHostTableColumnsCurated(
- HostsType.page,
- startDate,
- endDate,
- interval,
- narrowDateRange
- );
- const column = columns.find((col) => col.name === i18n.SCORE) as Columns<
- string,
- AnomaliesByHost
- >;
- const anomaly: AnomaliesByHost = {
- hostName: 'host.name',
- anomaly: {
- detectorIndex: 0,
- entityName: 'entity-name-1',
- entityValue: 'entity-value-1',
- influencers: [],
- jobId: 'job-1',
- rowId: 'row-1',
- severity: 100,
- time: new Date('01/01/2000').valueOf(),
- source: {
- job_id: 'job-1',
- result_type: 'result-1',
- probability: 50,
- multi_bucket_impact: 0,
- record_score: 0,
- initial_record_score: 0,
- bucket_span: 0,
- detector_index: 0,
- is_interim: true,
- timestamp: new Date('01/01/2000').valueOf(),
- by_field_name: 'some field name',
- by_field_value: 'some field value',
- partition_field_name: 'partition field name',
- partition_field_value: 'partition field value',
- function: 'function-1',
- function_description: 'description-1',
- typical: [5, 3],
- actual: [7, 4],
- influencers: [],
- },
- },
- };
- if (column != null && column.render != null) {
- const wrapper = mount({column.render('', anomaly)} );
- expect(
- wrapper
- .find(
- '[draggableId="draggableId.content.anomalies-host-table-severity-host_name-entity-name-1-entity-value-1-100-job-1"]'
- )
- .first()
- .exists()
- ).toBe(true);
- } else {
- expect(column).not.toBe(null);
- }
- });
-
test('on host page, undefined influencers should turn into an empty column string', () => {
const columns = getAnomaliesHostTableColumnsCurated(
HostsType.page,
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 48c2ec3ee38d8..817205ce22808 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
@@ -43,62 +43,6 @@ describe('get_anomalies_network_table_columns', () => {
expect(columns.some((col) => col.name === i18n.NETWORK_NAME)).toEqual(false);
});
- test('on network page, we should escape the draggable id', () => {
- const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
- const column = columns.find((col) => col.name === i18n.SCORE) as Columns<
- string,
- AnomaliesByNetwork
- >;
- const anomaly: AnomaliesByNetwork = {
- type: 'source.ip',
- ip: '127.0.0.1',
- anomaly: {
- detectorIndex: 0,
- entityName: 'entity-name-1',
- entityValue: 'entity-value-1',
- influencers: [],
- jobId: 'job-1',
- rowId: 'row-1',
- severity: 100,
- time: new Date('01/01/2000').valueOf(),
- source: {
- job_id: 'job-1',
- result_type: 'result-1',
- probability: 50,
- multi_bucket_impact: 0,
- record_score: 0,
- initial_record_score: 0,
- bucket_span: 0,
- detector_index: 0,
- is_interim: true,
- timestamp: new Date('01/01/2000').valueOf(),
- by_field_name: 'some field name',
- by_field_value: 'some field value',
- partition_field_name: 'partition field name',
- partition_field_value: 'partition field value',
- function: 'function-1',
- function_description: 'description-1',
- typical: [5, 3],
- actual: [7, 4],
- influencers: [],
- },
- },
- };
- if (column != null && column.render != null) {
- const wrapper = mount({column.render('', anomaly)} );
- expect(
- wrapper
- .find(
- '[draggableId="draggableId.content.anomalies-network-table-severity-127_0_0_1-entity-name-1-entity-value-1-100-job-1"]'
- )
- .first()
- .exists()
- ).toBe(true);
- } else {
- expect(column).not.toBe(null);
- }
- });
-
test('on network page, undefined influencers should turn into an empty column string', () => {
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
const column = columns.find((col) => col.name === i18n.INFLUENCED_BY) as 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 c122138f9547a..10e4538c802ad 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
@@ -55,7 +55,7 @@ describe('Table Helpers', () => {
displayCount: 0,
});
const wrapper = mount({rowItem} );
- expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
+ expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
'(Empty String)'
);
});
@@ -81,7 +81,7 @@ describe('Table Helpers', () => {
render: renderer,
});
const wrapper = mount({rowItem} );
- expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
+ expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
'Hi item1 renderer'
);
});
@@ -116,7 +116,7 @@ describe('Table Helpers', () => {
idPrefix: 'idPrefix',
});
const wrapper = mount({rowItems} );
- expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
+ expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
'(Empty String)'
);
});
@@ -163,7 +163,7 @@ describe('Table Helpers', () => {
displayCount: 2,
});
const wrapper = mount({rowItems} );
- expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').hostNodes().length).toBe(2);
+ expect(wrapper.find('[data-test-subj="withHoverActionsButton"]').hostNodes().length).toBe(2);
});
test('it uses custom renderer', () => {
@@ -175,7 +175,7 @@ describe('Table Helpers', () => {
render: renderer,
});
const wrapper = mount({rowItems} );
- expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
+ expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
'Hi item1 renderer'
);
});
diff --git a/x-pack/plugins/security_solution/public/network/components/direction/index.tsx b/x-pack/plugins/security_solution/public/network/components/direction/index.tsx
index 7c6bb50378d9c..d87756cb9bbab 100644
--- a/x-pack/plugins/security_solution/public/network/components/direction/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/direction/index.tsx
@@ -60,13 +60,15 @@ export const DirectionBadge = React.memo<{
contextId: string;
direction?: string | null;
eventId: string;
-}>(({ contextId, eventId, direction }) => (
+ isDraggable?: boolean;
+}>(({ contextId, eventId, direction, isDraggable }) => (
));
diff --git a/x-pack/plugins/security_solution/public/network/components/ip/index.tsx b/x-pack/plugins/security_solution/public/network/components/ip/index.tsx
index a08b8003f142c..2fa3e988784ca 100644
--- a/x-pack/plugins/security_solution/public/network/components/ip/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/ip/index.tsx
@@ -22,13 +22,15 @@ export const Ip = React.memo<{
contextId: string;
eventId: string;
fieldName: string;
+ isDraggable?: boolean;
value?: string | null;
-}>(({ contextId, eventId, fieldName, value }) => (
+}>(({ contextId, eventId, fieldName, isDraggable, value }) => (
diff --git a/x-pack/plugins/security_solution/public/network/components/port/index.tsx b/x-pack/plugins/security_solution/public/network/components/port/index.tsx
index df288c1abfb06..4afd9bc7b892a 100644
--- a/x-pack/plugins/security_solution/public/network/components/port/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/port/index.tsx
@@ -29,7 +29,7 @@ export const Port = React.memo<{
contextId: string;
eventId: string;
fieldName: string;
- isDraggable: boolean;
+ isDraggable?: boolean;
value: string | undefined | null;
}>(({ contextId, eventId, fieldName, isDraggable, value }) =>
isDraggable ? (
@@ -37,6 +37,7 @@ export const Port = React.memo<{
data-test-subj="port"
field={fieldName}
id={`port-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
+ isDraggable={isDraggable}
tooltipContent={fieldName}
value={value}
>
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/geo_fields.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/geo_fields.tsx
index 1f6111cd0bb07..65bd3bf1ec154 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/geo_fields.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/geo_fields.tsx
@@ -73,8 +73,9 @@ const GeoFieldValues = React.memo<{
contextId: string;
eventId: string;
fieldName: string;
+ isDraggable?: boolean;
values?: string[] | null;
-}>(({ contextId, eventId, fieldName, values }) =>
+}>(({ contextId, eventId, fieldName, isDraggable, values }) =>
values != null ? (
<>
{uniq(values).map((value) => (
@@ -92,6 +93,7 @@ const GeoFieldValues = React.memo<{
data-test-subj={fieldName}
field={fieldName}
id={`geo-field-values-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
+ isDraggable={isDraggable}
tooltipContent={fieldName}
value={value}
/>
@@ -114,7 +116,7 @@ GeoFieldValues.displayName = 'GeoFieldValues';
* - `source|destination.geo.city_name`
*/
export const GeoFields = React.memo((props) => {
- const { contextId, eventId, type } = props;
+ const { contextId, eventId, isDraggable, type } = props;
const propNameToFieldName = getGeoFieldPropNameToFieldNameMap(type);
return (
@@ -124,6 +126,7 @@ export const GeoFields = React.memo((props) => {
contextId={contextId}
eventId={eventId}
fieldName={geo.fieldName}
+ isDraggable={isDraggable}
key={geo.fieldName}
values={get(geo.prop, props)}
/>
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/index.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/index.tsx
index 57e302d2911fa..d7bcf9f6c5297 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/index.tsx
@@ -36,6 +36,7 @@ export const SourceDestination = React.memo(
destinationPackets,
destinationPort,
eventId,
+ isDraggable,
networkBytes,
networkCommunityId,
networkDirection,
@@ -59,8 +60,9 @@ export const SourceDestination = React.memo(
packets={networkPackets}
communityId={networkCommunityId}
contextId={contextId}
- eventId={eventId}
direction={networkDirection}
+ eventId={eventId}
+ isDraggable={isDraggable}
protocol={networkProtocol}
transport={transport}
/>
@@ -79,6 +81,7 @@ export const SourceDestination = React.memo(
destinationPackets={destinationPackets}
destinationPort={destinationPort}
eventId={eventId}
+ isDraggable={isDraggable}
sourceBytes={sourceBytes}
sourceGeoContinentName={sourceGeoContinentName}
sourceGeoCountryName={sourceGeoCountryName}
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/ip_with_port.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/ip_with_port.tsx
index 17b55c4229fcc..e99aecbc535e7 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/ip_with_port.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/ip_with_port.tsx
@@ -25,9 +25,10 @@ IpPortSeparator.displayName = 'IpPortSeparator';
const PortWithSeparator = React.memo<{
contextId: string;
eventId: string;
+ isDraggable?: boolean;
port?: string | null;
portFieldName: string;
-}>(({ contextId, eventId, port, portFieldName }) => {
+}>(({ contextId, eventId, isDraggable, port, portFieldName }) => {
return port != null ? (
@@ -39,7 +40,7 @@ const PortWithSeparator = React.memo<{
data-test-subj="port"
eventId={eventId}
fieldName={portFieldName}
- isDraggable={true}
+ isDraggable={isDraggable}
value={port}
/>
@@ -58,9 +59,10 @@ export const IpWithPort = React.memo<{
eventId: string;
ip?: string | null;
ipFieldName: string;
+ isDraggable?: boolean;
port?: string | null;
portFieldName: string;
-}>(({ contextId, eventId, ip, ipFieldName, port, portFieldName }) => (
+}>(({ contextId, eventId, ip, ipFieldName, isDraggable, port, portFieldName }) => (
@@ -75,6 +78,7 @@ export const IpWithPort = React.memo<{
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/network.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/network.tsx
index c1b454892fddf..88bfd19b7066e 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/network.tsx
@@ -45,97 +45,120 @@ export const Network = React.memo<{
contextId: string;
direction?: string[] | null;
eventId: string;
+ isDraggable?: boolean;
packets?: string[] | null;
protocol?: string[] | null;
transport?: string[] | null;
-}>(({ bytes, communityId, contextId, direction, eventId, packets, protocol, transport }) => (
-
- {direction != null
- ? uniq(direction).map((dir) => (
-
-
-
- ))
- : null}
+}>(
+ ({
+ bytes,
+ communityId,
+ contextId,
+ direction,
+ eventId,
+ isDraggable,
+ packets,
+ protocol,
+ transport,
+ }) => (
+
+ {direction != null
+ ? uniq(direction).map((dir) => (
+
+
+
+ ))
+ : null}
+
+ {protocol != null
+ ? uniq(protocol).map((proto) => (
+
+
+
+ ))
+ : null}
- {protocol != null
- ? uniq(protocol).map((proto) => (
-
-
-
- ))
- : null}
+ {bytes != null
+ ? uniq(bytes).map((b) =>
+ !isNaN(Number(b)) ? (
+
+
+
+
+
+
+
+
+
+ ) : null
+ )
+ : null}
- {bytes != null
- ? uniq(bytes).map((b) =>
- !isNaN(Number(b)) ? (
-
+ {packets != null
+ ? uniq(packets).map((p) => (
+
-
-
-
+ {`${p} ${i18n.PACKETS}`}
- ) : null
- )
- : null}
+ ))
+ : null}
- {packets != null
- ? uniq(packets).map((p) => (
-
-
-
- {`${p} ${i18n.PACKETS}`}
-
-
-
- ))
- : null}
-
- {transport != null
- ? uniq(transport).map((trans) => (
-
-
-
- ))
- : null}
+ {transport != null
+ ? uniq(transport).map((trans) => (
+
+
+
+ ))
+ : null}
- {communityId != null
- ? uniq(communityId).map((trans) => (
-
-
-
- ))
- : null}
-
-));
+ {communityId != null
+ ? uniq(communityId).map((trans) => (
+
+
+
+ ))
+ : null}
+
+ )
+);
Network.displayName = 'Network';
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
index ff9edff39b3ad..6858520340aae 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
@@ -56,10 +56,11 @@ Data.displayName = 'Data';
const SourceArrow = React.memo<{
contextId: string;
eventId: string;
+ isDraggable?: boolean;
sourceBytes: string | undefined;
sourceBytesPercent: number | undefined;
sourcePackets: string | undefined;
-}>(({ contextId, eventId, sourceBytes, sourceBytesPercent, sourcePackets }) => {
+}>(({ contextId, eventId, isDraggable, sourceBytes, sourceBytesPercent, sourcePackets }) => {
const sourceArrowHeight =
sourceBytesPercent != null
? getArrowHeightFromPercent(sourceBytesPercent)
@@ -76,6 +77,7 @@ const SourceArrow = React.memo<{
@@ -101,6 +103,7 @@ const SourceArrow = React.memo<{
@@ -129,73 +132,85 @@ SourceArrow.displayName = 'SourceArrow';
*/
const DestinationArrow = React.memo<{
contextId: string;
- eventId: string;
destinationBytes: string | undefined;
destinationBytesPercent: number | undefined;
destinationPackets: string | undefined;
-}>(({ contextId, eventId, destinationBytes, destinationBytesPercent, destinationPackets }) => {
- const destinationArrowHeight =
- destinationBytesPercent != null
- ? getArrowHeightFromPercent(destinationBytesPercent)
- : DEFAULT_ARROW_HEIGHT;
+ eventId: string;
+ isDraggable?: boolean;
+}>(
+ ({
+ contextId,
+ destinationBytes,
+ destinationBytesPercent,
+ destinationPackets,
+ eventId,
+ isDraggable,
+ }) => {
+ const destinationArrowHeight =
+ destinationBytesPercent != null
+ ? getArrowHeightFromPercent(destinationBytesPercent)
+ : DEFAULT_ARROW_HEIGHT;
+
+ return (
+
+
+
+
- return (
-
-
-
-
+
+
+
-
-
-
+ {destinationBytes != null && !isNaN(Number(destinationBytes)) ? (
+
+
+
+ {destinationBytesPercent != null ? (
+
+ {`(${numeral(destinationBytesPercent).format('0.00')}%)`}
+
+ ) : null}
+
+
+
+
+
+
+ ) : null}
- {destinationBytes != null && !isNaN(Number(destinationBytes)) ? (
-
-
- {destinationBytesPercent != null ? (
-
- {`(${numeral(destinationBytesPercent).format('0.00')}%)`}
-
- ) : null}
-
-
-
-
-
+
- ) : null}
-
-
-
+ {destinationPackets != null && !isNaN(Number(destinationPackets)) ? (
+
+
+
+ {`${numeral(destinationPackets).format(
+ '0,0'
+ )} ${i18n.PACKETS}`}
+
+
+
+ ) : null}
- {destinationPackets != null && !isNaN(Number(destinationPackets)) ? (
-
-
- {`${numeral(destinationPackets).format(
- '0,0'
- )} ${i18n.PACKETS}`}
-
-
+
- ) : null}
-
-
-
-
-
- );
-});
+
+ );
+ }
+);
DestinationArrow.displayName = 'DestinationArrow';
@@ -208,67 +223,79 @@ export const SourceDestinationArrows = React.memo<{
destinationBytes?: string[] | null;
destinationPackets?: string[] | null;
eventId: string;
+ isDraggable?: boolean;
sourceBytes?: string[] | null;
sourcePackets?: string[] | null;
-}>(({ contextId, destinationBytes, destinationPackets, eventId, sourceBytes, sourcePackets }) => {
- const maybeSourceBytes =
- sourceBytes != null && hasOneValue(sourceBytes) ? sourceBytes[0] : undefined;
-
- const maybeSourcePackets =
- sourcePackets != null && hasOneValue(sourcePackets) ? sourcePackets[0] : undefined;
-
- const maybeDestinationBytes =
- destinationBytes != null && hasOneValue(destinationBytes) ? destinationBytes[0] : undefined;
-
- const maybeDestinationPackets =
- destinationPackets != null && hasOneValue(destinationPackets)
- ? destinationPackets[0]
- : undefined;
-
- const maybeSourceBytesPercent =
- maybeSourceBytes != null && maybeDestinationBytes != null
- ? getPercent({
- numerator: Number(maybeSourceBytes),
- denominator: Number(maybeSourceBytes) + Number(maybeDestinationBytes),
- })
- : undefined;
-
- const maybeDestinationBytesPercent =
- maybeSourceBytesPercent != null ? 100 - maybeSourceBytesPercent : undefined;
-
- return (
-
- {maybeSourceBytes != null ? (
-
-
-
- ) : null}
-
- {maybeDestinationBytes != null ? (
-
-
-
- ) : null}
-
- );
-});
+}>(
+ ({
+ contextId,
+ destinationBytes,
+ destinationPackets,
+ eventId,
+ isDraggable,
+ sourceBytes,
+ sourcePackets,
+ }) => {
+ const maybeSourceBytes =
+ sourceBytes != null && hasOneValue(sourceBytes) ? sourceBytes[0] : undefined;
+
+ const maybeSourcePackets =
+ sourcePackets != null && hasOneValue(sourcePackets) ? sourcePackets[0] : undefined;
+
+ const maybeDestinationBytes =
+ destinationBytes != null && hasOneValue(destinationBytes) ? destinationBytes[0] : undefined;
+
+ const maybeDestinationPackets =
+ destinationPackets != null && hasOneValue(destinationPackets)
+ ? destinationPackets[0]
+ : undefined;
+
+ const maybeSourceBytesPercent =
+ maybeSourceBytes != null && maybeDestinationBytes != null
+ ? getPercent({
+ numerator: Number(maybeSourceBytes),
+ denominator: Number(maybeSourceBytes) + Number(maybeDestinationBytes),
+ })
+ : undefined;
+
+ const maybeDestinationBytesPercent =
+ maybeSourceBytesPercent != null ? 100 - maybeSourceBytesPercent : undefined;
+
+ return (
+
+ {maybeSourceBytes != null ? (
+
+
+
+ ) : null}
+ {maybeDestinationBytes != null ? (
+
+
+
+ ) : null}
+
+ );
+ }
+);
SourceDestinationArrows.displayName = 'SourceDestinationArrows';
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 91f7ea3d7ac7a..824b9fd11f242 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
@@ -958,6 +958,7 @@ describe('SourceDestinationIp', () => {
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
+ isDraggable={true}
sourceGeoContinentName={asArrayIfExists(
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
)}
@@ -979,7 +980,6 @@ describe('SourceDestinationIp', () => {
/>
);
-
expect(
removeExternalLinkText(
wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text()
@@ -1011,6 +1011,7 @@ describe('SourceDestinationIp', () => {
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
+ isDraggable={true}
sourceGeoContinentName={asArrayIfExists(
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
)}
@@ -1064,6 +1065,7 @@ describe('SourceDestinationIp', () => {
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
+ isDraggable={true}
sourceGeoContinentName={asArrayIfExists(
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
)}
@@ -1118,6 +1120,7 @@ describe('SourceDestinationIp', () => {
destinationIp={undefined}
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
+ isDraggable={true}
sourceGeoContinentName={asArrayIfExists(
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
)}
@@ -1271,6 +1274,7 @@ describe('SourceDestinationIp', () => {
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
+ isDraggable={true}
sourceGeoContinentName={asArrayIfExists(
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
)}
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.tsx
index db9773789bf54..31bae6880fcbe 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.tsx
@@ -88,54 +88,67 @@ const IpAdressesWithPorts = React.memo<{
destinationIp?: string[] | null;
destinationPort?: Array | null;
eventId: string;
+ isDraggable?: boolean;
sourceIp?: string[] | null;
sourcePort?: Array | null;
type: SourceDestinationType;
-}>(({ contextId, destinationIp, destinationPort, eventId, sourceIp, sourcePort, type }) => {
- const ip = type === 'source' ? sourceIp : destinationIp;
- const ipFieldName = type === 'source' ? SOURCE_IP_FIELD_NAME : DESTINATION_IP_FIELD_NAME;
- const port = type === 'source' ? sourcePort : destinationPort;
- const portFieldName = type === 'source' ? SOURCE_PORT_FIELD_NAME : DESTINATION_PORT_FIELD_NAME;
-
- if (ip == null) {
- return null; // if ip is not populated as an array, ports will be ignored
+}>(
+ ({
+ contextId,
+ destinationIp,
+ destinationPort,
+ eventId,
+ isDraggable,
+ sourceIp,
+ sourcePort,
+ type,
+ }) => {
+ const ip = type === 'source' ? sourceIp : destinationIp;
+ const ipFieldName = type === 'source' ? SOURCE_IP_FIELD_NAME : DESTINATION_IP_FIELD_NAME;
+ const port = type === 'source' ? sourcePort : destinationPort;
+ const portFieldName = type === 'source' ? SOURCE_PORT_FIELD_NAME : DESTINATION_PORT_FIELD_NAME;
+
+ if (ip == null) {
+ return null; // if ip is not populated as an array, ports will be ignored
+ }
+
+ // IMPORTANT: The ip and port arrays are parallel arrays; the port at
+ // index `i` corresponds with the ip address at index `i`. We must
+ // preserve the relationships between the parallel arrays:
+ const ipPortPairs: IpPortPair[] =
+ port != null && ip.length === port.length
+ ? ip.map((address, i) => ({
+ ip: address,
+ port: port[i] != null ? `${port[i]}` : null, // use the corresponding port in the parallel array
+ }))
+ : ip.map((address) => ({
+ ip: address,
+ port: null, // drop the port, because the length of the parallel ip and port arrays is different
+ }));
+
+ return (
+
+ {uniqWith(deepEqual, ipPortPairs).map(
+ (ipPortPair) =>
+ ipPortPair.ip != null && (
+
+
+
+ )
+ )}
+
+ );
}
-
- // IMPORTANT: The ip and port arrays are parallel arrays; the port at
- // index `i` corresponds with the ip address at index `i`. We must
- // preserve the relationships between the parallel arrays:
- const ipPortPairs: IpPortPair[] =
- port != null && ip.length === port.length
- ? ip.map((address, i) => ({
- ip: address,
- port: port[i] != null ? `${port[i]}` : null, // use the corresponding port in the parallel array
- }))
- : ip.map((address) => ({
- ip: address,
- port: null, // drop the port, because the length of the parallel ip and port arrays is different
- }));
-
- return (
-
- {uniqWith(deepEqual, ipPortPairs).map(
- (ipPortPair) =>
- ipPortPair.ip != null && (
-
-
-
- )
- )}
-
- );
-});
+);
IpAdressesWithPorts.displayName = 'IpAdressesWithPorts';
@@ -159,6 +172,7 @@ export const SourceDestinationIp = React.memo(
destinationIp,
destinationPort,
eventId,
+ isDraggable,
sourceGeoContinentName,
sourceGeoCountryName,
sourceGeoCountryIsoCode,
@@ -189,6 +203,7 @@ export const SourceDestinationIp = React.memo(
destinationIp={destinationIp}
destinationPort={destinationPort}
eventId={eventId}
+ isDraggable={isDraggable}
sourceIp={sourceIp}
sourcePort={sourcePort}
type={type}
@@ -202,7 +217,7 @@ export const SourceDestinationIp = React.memo(
data-test-subj="port"
eventId={eventId}
fieldName={`${type}.port`}
- isDraggable={true}
+ isDraggable={isDraggable}
value={port}
/>
@@ -219,6 +234,7 @@ export const SourceDestinationIp = React.memo(
destinationGeoRegionName={destinationGeoRegionName}
destinationGeoCityName={destinationGeoCityName}
eventId={eventId}
+ isDraggable={isDraggable}
sourceGeoContinentName={sourceGeoContinentName}
sourceGeoCountryName={sourceGeoCountryName}
sourceGeoCountryIsoCode={sourceGeoCountryIsoCode}
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_with_arrows.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_with_arrows.tsx
index 3d6189118ecb0..a010d674291ba 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_with_arrows.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_with_arrows.tsx
@@ -32,6 +32,7 @@ export const SourceDestinationWithArrows = React.memo
@@ -85,6 +88,7 @@ export const SourceDestinationWithArrows = React.memo | null;
eventId: string;
+ isDraggable?: boolean;
sourceGeoContinentName?: string[] | null;
sourceGeoCountryName?: string[] | null;
sourceGeoCountryIsoCode?: string[] | null;
@@ -85,6 +88,7 @@ export interface SourceDestinationWithArrowsProps {
destinationPackets?: string[] | null;
destinationPort?: string[] | null;
eventId: string;
+ isDraggable?: boolean;
sourceBytes?: string[] | null;
sourceGeoContinentName?: string[] | null;
sourceGeoCountryName?: string[] | null;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx
index 29775067478a5..296faf208ac91 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx
@@ -40,8 +40,9 @@ export const CertificateFingerprint = React.memo<{
certificateType: CertificateType;
contextId: string;
fieldName: string;
+ isDraggable?: boolean;
value?: string | null;
-}>(({ eventId, certificateType, contextId, fieldName, value }) => {
+}>(({ eventId, certificateType, contextId, fieldName, isDraggable, value }) => {
return (
{fieldName}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/duration/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/duration/index.tsx
index 421ba5941eaef..7500fdb122fae 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/duration/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/duration/index.tsx
@@ -26,6 +26,7 @@ export const Duration = React.memo<{
isDraggable ? (
@@ -24,6 +25,7 @@ exports[`Field Renderers #autonomousSystemRenderer it renders correctly against
@@ -58,6 +60,7 @@ exports[`Field Renderers #hostIdRenderer it renders correctly against snapshot 1
},
}
}
+ isDraggable={false}
render={[Function]}
/>
`;
@@ -79,6 +82,7 @@ exports[`Field Renderers #hostNameRenderer it renders correctly against snapshot
},
}
}
+ isDraggable={false}
render={[Function]}
/>
`;
@@ -94,6 +98,7 @@ exports[`Field Renderers #locationRenderer it renders correctly against snapshot
@@ -84,6 +85,7 @@ export const autonomousSystemRenderer = (
id={`autonomous-system-renderer-default-draggable-${IpOverviewId}-${
contextID ? `${contextID}-` : ''
}${flowTarget}.as.organization.name`}
+ isDraggable={false}
field={`${flowTarget}.as.organization.name`}
value={as.organization.name}
/>
@@ -94,6 +96,7 @@ export const autonomousSystemRenderer = (
id={`autonomous-system-renderer-default-draggable-${IpOverviewId}-${
contextID ? `${contextID}-` : ''
}${flowTarget}.as.number`}
+ isDraggable={false}
field={`${flowTarget}.as.number`}
value={`${as.number}`}
/>
@@ -123,6 +126,7 @@ export const hostIdRenderer = ({
id={`host-id-renderer-default-draggable-${IpOverviewId}-${
contextID ? `${contextID}-` : ''
}host-id`}
+ isDraggable={false}
field="host.id"
value={host.id[0]}
>
@@ -154,6 +158,7 @@ export const hostNameRenderer = (
id={`host-name-renderer-default-draggable-${IpOverviewId}-${
contextID ? `${contextID}-` : ''
}host-name`}
+ isDraggable={false}
field={'host.name'}
value={host.name[0]}
>
@@ -204,7 +209,7 @@ export const DefaultFieldRendererComponent: React.FC
>
)}
{typeof rowItem === 'string' && (
-
+
{render ? render(rowItem) : rowItem}
)}
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 5014a198e8bd5..5acc0ef9aa46b 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
@@ -59,11 +59,11 @@ describe('FieldName', () => {
);
await waitFor(() => {
- wrapper.find('[data-test-subj="withHoverActionsButton"]').at(0).simulate('mouseenter');
+ wrapper.find('[data-test-subj="withHoverActionsButton"]').simulate('mouseenter');
wrapper.update();
jest.runAllTimers();
wrapper.update();
- expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(true);
});
});
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 2e76e43227506..1e081d249cc00 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
@@ -11,11 +11,9 @@ import styled from 'styled-components';
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 { useGetTimelineId } from '../../../common/components/drag_and_drop/use_get_timeline_id_from_dom';
import { ColumnHeaderOptions } from '../../../../common';
+import { HoverActions } from '../../../common/components/hover_actions';
/**
* The name of a (draggable) field
@@ -112,9 +110,10 @@ export const FieldName = React.memo<{
const hoverContent = useMemo(
() => (
- = ({
return (
(({ contextId, eventId, fieldName, value }) => (
+}>(({ contextId, eventId, fieldName, isDraggable, value }) => (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/fingerprints/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/fingerprints/index.tsx
index 16ea48890778e..328d310524070 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/fingerprints/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/fingerprints/index.tsx
@@ -23,6 +23,7 @@ import { JA3_HASH_FIELD_NAME, Ja3Fingerprint } from '../../ja3_fingerprint';
export const Fingerprints = React.memo<{
contextId: string;
eventId: string;
+ isDraggable?: boolean;
tlsClientCertificateFingerprintSha1?: string[] | null;
tlsFingerprintsJa3Hash?: string[] | null;
tlsServerCertificateFingerprintSha1?: string[] | null;
@@ -30,6 +31,7 @@ export const Fingerprints = React.memo<{
({
contextId,
eventId,
+ isDraggable,
tlsClientCertificateFingerprintSha1,
tlsFingerprintsJa3Hash,
tlsServerCertificateFingerprintSha1,
@@ -48,6 +50,7 @@ export const Fingerprints = React.memo<{
eventId={eventId}
fieldName={JA3_HASH_FIELD_NAME}
contextId={contextId}
+ isDraggable={isDraggable}
value={ja3}
/>
@@ -61,6 +64,7 @@ export const Fingerprints = React.memo<{
certificateType="client"
contextId={contextId}
fieldName={TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME}
+ isDraggable={isDraggable}
value={clientCert}
/>
@@ -74,6 +78,7 @@ export const Fingerprints = React.memo<{
certificateType="server"
contextId={contextId}
fieldName={TLS_SERVER_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME}
+ isDraggable={isDraggable}
value={serverCert}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.tsx
index 05bfe56d1df42..a755aa54fca7b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.tsx
@@ -37,6 +37,7 @@ export const Netflow = React.memo(
eventId,
eventEnd,
eventStart,
+ isDraggable,
networkBytes,
networkCommunityId,
networkDirection,
@@ -82,6 +83,7 @@ export const Netflow = React.memo(
eventId={eventId}
eventEnd={eventEnd}
eventStart={eventStart}
+ isDraggable={isDraggable}
networkBytes={networkBytes}
networkCommunityId={networkCommunityId}
networkDirection={networkDirection}
@@ -105,6 +107,7 @@ export const Netflow = React.memo(
(({ contextId, eventDuration, eventId, eventEnd, eventStart }) => (
+ isDraggable?: boolean;
+}>(({ contextId, eventDuration, eventId, eventEnd, eventStart, isDraggable }) => (
@@ -94,6 +97,7 @@ export const DurationEventStartEnd = React.memo<{
data-test-subj="event-end"
field={EVENT_END_FIELD_NAME}
id={`duration-event-start-end-default-draggable-${contextId}-${eventId}-${EVENT_END_FIELD_NAME}-${end}`}
+ isDraggable={isDraggable}
tooltipContent={null}
value={end}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/index.tsx
index 4714b561f036b..e319e803e63fe 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/index.tsx
@@ -48,6 +48,7 @@ export const NetflowColumns = React.memo(
eventId,
eventEnd,
eventStart,
+ isDraggable,
networkBytes,
networkCommunityId,
networkDirection,
@@ -76,6 +77,7 @@ export const NetflowColumns = React.memo(
@@ -88,6 +90,7 @@ export const NetflowColumns = React.memo(
eventId={eventId}
eventEnd={eventEnd}
eventStart={eventStart}
+ isDraggable={isDraggable}
/>
@@ -104,6 +107,7 @@ export const NetflowColumns = React.memo(
destinationPackets={destinationPackets}
destinationPort={destinationPort}
eventId={eventId}
+ isDraggable={isDraggable}
networkBytes={networkBytes}
networkCommunityId={networkCommunityId}
networkDirection={networkDirection}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/types.ts b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/types.ts
index 532b35f4cffd0..801df93bfcf37 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/types.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/types.ts
@@ -21,6 +21,7 @@ export interface NetflowColumnsProps {
eventId: string;
eventEnd?: string[] | null;
eventStart?: string[] | null;
+ isDraggable?: boolean;
networkBytes?: string[] | null;
networkCommunityId?: string[] | null;
networkDirection?: string[] | null;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/user_process.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/user_process.tsx
index e6931baeb7017..72de537fee588 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/user_process.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/netflow_columns/user_process.tsx
@@ -22,9 +22,10 @@ export const USER_NAME_FIELD_NAME = 'user.name';
export const UserProcess = React.memo<{
contextId: string;
eventId: string;
+ isDraggable?: boolean;
processName?: string[] | null;
userName?: string[] | null;
-}>(({ contextId, eventId, processName, userName }) => (
+}>(({ contextId, eventId, isDraggable, processName, userName }) => (
@@ -55,6 +57,7 @@ export const UserProcess = React.memo<{
data-test-subj="process-name"
eventId={eventId}
field={PROCESS_NAME_FIELD_NAME}
+ isDraggable={isDraggable}
value={process}
iconType="console"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/types.ts b/x-pack/plugins/security_solution/public/timelines/components/netflow/types.ts
index a28334e2d45fb..0798345c61da9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/netflow/types.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/types.ts
@@ -20,6 +20,7 @@ export interface NetflowProps {
eventId: string;
eventEnd?: string[] | null;
eventStart?: string[] | null;
+ isDraggable?: boolean;
networkBytes?: string[] | null;
networkCommunityId?: string[] | null;
networkDirection?: string[] | null;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
index b0384155c5c10..d6aa34f2528e5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
@@ -26,6 +26,7 @@ const AlertsExampleComponent: React.FC = () => {
{alertsRowRenderer.renderRow({
browserFields: {},
data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
index 703621bc4c666..2c6ce5886462b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
@@ -23,6 +23,7 @@ const AuditdExampleComponent: React.FC = () => {
{auditdRowRenderer.renderRow({
browserFields: {},
data: mockTimelineData[26].ecs,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
index 265a71ef264d1..a525b26571dc8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
@@ -23,6 +23,7 @@ const AuditdFileExampleComponent: React.FC = () => {
{auditdFileRowRenderer.renderRow({
browserFields: {},
data: mockTimelineData[27].ecs,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
index 6198225fcb87d..f8704b63fe47e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
@@ -23,6 +23,7 @@ const LibraryExampleComponent: React.FC = () => {
{libraryRowRenderer.renderRow({
browserFields: {},
data: mockEndpointLibraryLoadEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
index cd20b28203246..c5a0f09440899 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
@@ -16,6 +16,7 @@ const NetflowExampleComponent: React.FC = () => (
{netflowRowRenderer.renderRow({
browserFields: {},
data: getMockNetflowData(),
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
index f00db0d94eed8..67859db1a5ea4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
@@ -23,6 +23,7 @@ const RegistryExampleComponent: React.FC = () => {
{registryRowRenderer.renderRow({
browserFields: {},
data: mockEndpointRegistryModificationEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
index f22ac0dca6f9d..1e6caca2effa9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
@@ -16,6 +16,7 @@ const SuricataExampleComponent: React.FC = () => (
{suricataRowRenderer.renderRow({
browserFields: {},
data: mockTimelineData[2].ecs,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
index 909d5224fb351..7d38f8feaace6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
@@ -23,6 +23,7 @@ const SystemExampleComponent: React.FC = () => {
{systemRowRenderer.renderRow({
browserFields: {},
data: mockEndgameTerminationEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
index 0f413eed811be..72c5060b27701 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
@@ -19,6 +19,7 @@ const SystemDnsExampleComponent: React.FC = () => {
{systemDnsRowRenderer.renderRow({
browserFields: {},
data: mockEndgameDnsRequest,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
index 0e5fe1768c787..6103746b3238b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
@@ -23,6 +23,7 @@ const SystemEndgameProcessExampleComponent: React.FC = () => {
{systemEndgameProcessRowRenderer.renderRow({
browserFields: {},
data: mockEndgameCreationEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
index 3db9a93fc37c9..cb8668536f8d2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
@@ -23,6 +23,7 @@ const SystemFileExampleComponent: React.FC = () => {
{systemFileRowRenderer.renderRow({
browserFields: {},
data: mockEndgameFileDeleteEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
index 08ff6a5ddc7c9..12ad132131d1b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
@@ -23,6 +23,7 @@ const SystemFimExampleComponent: React.FC = () => {
{systemFimRowRenderer.renderRow({
browserFields: {},
data: mockEndgameFileCreateEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx
index 59b5fedbc82fa..8dfb0bf998738 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx
@@ -21,6 +21,7 @@ const SystemSecurityEventExampleComponent: React.FC = () => {
{systemSecurityEventRowRenderer.renderRow({
browserFields: {},
data: mockEndgameUserLogon,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx
index 5175145bae9d9..7fa430e812625 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx
@@ -22,6 +22,7 @@ const SystemSocketExampleComponent: React.FC = () => {
{systemSocketRowRenderer.renderRow({
browserFields: {},
data: mockEndgameIpv4ConnectionAcceptEvent,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/threat_match.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/threat_match.tsx
index 9d7e5d48315e3..73d458a23ca17 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/threat_match.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/threat_match.tsx
@@ -16,6 +16,7 @@ const ThreatMatchExampleComponent: React.FC = () => (
{threatMatchRowRenderer.renderRow({
browserFields: {},
data: mockTimelineData[31].ecs,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
index b84942ea8b2a8..83d9e0122e971 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
@@ -16,6 +16,7 @@ const ZeekExampleComponent: React.FC = () => (
{zeekRowRenderer.renderRow({
browserFields: {},
data: mockTimelineData[13].ecs,
+ isDraggable: false,
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
})}
>
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 19abd6841e7e8..cf1f4a26c709d 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
@@ -80,6 +80,7 @@ export const StatefulRowRenderer = ({
{rowRenderer.renderRow({
browserFields,
data: event.ecs,
+ isDraggable: true,
timelineId,
})}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap
index 92816d499b029..722c7a7aebb00 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap
@@ -19,6 +19,7 @@ exports[`empty_column_renderer renders correctly against snapshot 1`] = `
},
}
}
+ isDraggable={true}
key="empty-column-renderer-draggable-wrapper-test-source.ip-1-source.ip"
render={[Function]}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
index 417cf0ceee184..edec2d0d823fa 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
@@ -42,6 +42,7 @@ export const AgentStatuses = React.memo(
@@ -60,6 +61,7 @@ export const AgentStatuses = React.memo(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.tsx
index bdb650585bdb0..fbb2c2edf8ae3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.tsx
@@ -15,9 +15,10 @@ interface Props {
contextId: string;
eventId: string;
processTitle: string | null | undefined;
+ isDraggable?: boolean;
}
-export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props) => {
+export const ArgsComponent = ({ args, contextId, eventId, processTitle, isDraggable }: Props) => {
if (isNillEmptyOrNotFinite(args) && isNillEmptyOrNotFinite(processTitle)) {
return null;
}
@@ -31,6 +32,7 @@ export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props)
contextId={`${contextId}-args-${i}-${arg}`}
eventId={eventId}
field="process.args"
+ isDraggable={isDraggable}
value={arg}
/>
@@ -42,6 +44,7 @@ export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props)
contextId={contextId}
eventId={eventId}
field="process.title"
+ isDraggable={isDraggable}
value={processTitle}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
index 63b65d3cf36be..684764e39848f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
@@ -98,6 +98,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
},
}
}
+ isDraggable={true}
text="connected using"
timelineId="test"
/>
@@ -222,6 +223,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
}
}
fileIcon="document"
+ isDraggable={true}
text="opened file using"
timelineId="test"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.tsx
index 737d0b74bfbf9..fb14d44995c95 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.tsx
@@ -36,6 +36,7 @@ interface Props {
workingDirectory: string | null | undefined;
args: string[] | null | undefined;
session: string | null | undefined;
+ isDraggable?: boolean;
}
export const AuditdGenericLine = React.memo(
@@ -55,6 +56,7 @@ export const AuditdGenericLine = React.memo(
result,
session,
text,
+ isDraggable,
}) => (
(
secondary={secondary}
workingDirectory={workingDirectory}
session={session}
+ isDraggable={isDraggable}
/>
{processExecutable != null && (
@@ -81,9 +84,16 @@ export const AuditdGenericLine = React.memo(
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
+ isDraggable={isDraggable}
/>
-
+
{result != null && (
{i18n.WITH_RESULT}
@@ -94,6 +104,7 @@ export const AuditdGenericLine = React.memo(
contextId={contextId}
eventId={id}
field="auditd.result"
+ isDraggable={isDraggable}
queryValue={result}
value={result}
/>
@@ -107,13 +118,14 @@ AuditdGenericLine.displayName = 'AuditdGenericLine';
interface GenericDetailsProps {
browserFields: BrowserFields;
data: Ecs;
+ isDraggable?: boolean;
contextId: string;
text: string;
timelineId: string;
}
export const AuditdGenericDetails = React.memo(
- ({ data, contextId, text, timelineId }) => {
+ ({ data, contextId, isDraggable, text, timelineId }) => {
const id = data._id;
const session: string | null | undefined = get('auditd.session[0]', data);
const hostName: string | null | undefined = get('host.name[0]', data);
@@ -146,9 +158,10 @@ export const AuditdGenericDetails = React.memo(
primary={primary}
result={result}
secondary={secondary}
+ isDraggable={isDraggable}
/>
-
+
);
} else {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.tsx
index efab1a433c0bb..89fbbf751b0ee 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.tsx
@@ -38,6 +38,7 @@ interface Props {
workingDirectory: string | null | undefined;
args: string[] | null | undefined;
session: string | null | undefined;
+ isDraggable?: boolean;
}
export const AuditdGenericFileLine = React.memo(
@@ -59,6 +60,7 @@ export const AuditdGenericFileLine = React.memo(
session,
text,
fileIcon,
+ isDraggable,
}) => (
(
secondary={secondary}
workingDirectory={workingDirectory}
session={session}
+ isDraggable={isDraggable}
/>
{(filePath != null || processExecutable != null) && (
@@ -81,6 +84,7 @@ export const AuditdGenericFileLine = React.memo(
contextId={contextId}
eventId={id}
field="file.path"
+ isDraggable={isDraggable}
value={filePath}
iconType={fileIcon}
/>
@@ -96,12 +100,19 @@ export const AuditdGenericFileLine = React.memo(
endgamePid={undefined}
endgameProcessName={undefined}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
/>
-
+
{result != null && (
{i18n.WITH_RESULT}
@@ -112,6 +123,7 @@ export const AuditdGenericFileLine = React.memo(
contextId={contextId}
eventId={id}
field="auditd.result"
+ isDraggable={isDraggable}
queryValue={result}
value={result}
/>
@@ -124,15 +136,16 @@ AuditdGenericFileLine.displayName = 'AuditdGenericFileLine';
interface GenericDetailsProps {
browserFields: BrowserFields;
- data: Ecs;
contextId: string;
+ data: Ecs;
text: string;
fileIcon: IconType;
timelineId: string;
+ isDraggable?: boolean;
}
export const AuditdGenericFileDetails = React.memo(
- ({ data, contextId, text, fileIcon = 'document', timelineId }) => {
+ ({ data, contextId, text, fileIcon = 'document', timelineId, isDraggable }) => {
const id = data._id;
const session: string | null | undefined = get('auditd.session[0]', data);
const hostName: string | null | undefined = get('host.name[0]', data);
@@ -169,9 +182,10 @@ export const AuditdGenericFileDetails = React.memo(
secondary={secondary}
fileIcon={fileIcon}
result={result}
+ isDraggable={isDraggable}
/>
-
+
);
} else {
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 74a5ff472b581..1f44feb3b394f 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
@@ -55,6 +55,7 @@ describe('GenericRowRenderer', () => {
const children = connectedToRenderer.renderRow({
browserFields,
data: auditd,
+ isDraggable: true,
timelineId: 'test',
});
@@ -84,6 +85,7 @@ describe('GenericRowRenderer', () => {
const children = connectedToRenderer.renderRow({
browserFields: mockBrowserFields,
data: auditd,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -117,6 +119,7 @@ describe('GenericRowRenderer', () => {
const children = fileToRenderer.renderRow({
browserFields,
data: auditdFile,
+ isDraggable: true,
timelineId: 'test',
});
@@ -146,6 +149,7 @@ describe('GenericRowRenderer', () => {
const children = fileToRenderer.renderRow({
browserFields: mockBrowserFields,
data: auditdFile,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
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 765bfd3d21351..d0522e97157ab 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
@@ -36,11 +36,12 @@ export const createGenericAuditRowRenderer = ({
action.toLowerCase() === actionName
);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
(
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx
index 8fd8cfd5af9da..5857dc1e30182 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx
@@ -21,69 +21,77 @@ interface Props {
eventId: string;
primary: string | null | undefined;
secondary: string | null | undefined;
+ isDraggable?: boolean;
}
-export const PrimarySecondary = React.memo(({ contextId, eventId, primary, secondary }) => {
- if (nilOrUnSet(primary) && nilOrUnSet(secondary)) {
- return null;
- } else if (!nilOrUnSet(primary) && nilOrUnSet(secondary)) {
- return (
-
- );
- } else if (nilOrUnSet(primary) && !nilOrUnSet(secondary)) {
- return (
-
- );
- } else if (primary === secondary) {
- return (
-
- );
- } else {
- return (
-
-
-
-
-
- {i18n.AS}
-
-
-
-
-
- );
+export const PrimarySecondary = React.memo(
+ ({ contextId, eventId, primary, secondary, isDraggable }) => {
+ if (nilOrUnSet(primary) && nilOrUnSet(secondary)) {
+ return null;
+ } else if (!nilOrUnSet(primary) && nilOrUnSet(secondary)) {
+ return (
+
+ );
+ } else if (nilOrUnSet(primary) && !nilOrUnSet(secondary)) {
+ return (
+
+ );
+ } else if (primary === secondary) {
+ return (
+
+ );
+ } else {
+ return (
+
+
+
+
+
+ {i18n.AS}
+
+
+
+
+
+ );
+ }
}
-});
+);
PrimarySecondary.displayName = 'PrimarySecondary';
@@ -93,10 +101,11 @@ interface PrimarySecondaryUserInfoProps {
userName: string | null | undefined;
primary: string | null | undefined;
secondary: string | null | undefined;
+ isDraggable?: boolean;
}
export const PrimarySecondaryUserInfo = React.memo(
- ({ contextId, eventId, userName, primary, secondary }) => {
+ ({ contextId, eventId, userName, primary, secondary, isDraggable }) => {
if (nilOrUnSet(userName) && nilOrUnSet(primary) && nilOrUnSet(secondary)) {
return null;
} else if (
@@ -111,6 +120,7 @@ export const PrimarySecondaryUserInfo = React.memo
@@ -121,6 +131,7 @@ export const PrimarySecondaryUserInfo = React.memo
@@ -130,6 +141,7 @@ export const PrimarySecondaryUserInfo = React.memo
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx
index a7252064d9774..f90407b882fdf 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx
@@ -23,10 +23,21 @@ interface Props {
secondary: string | null | undefined;
workingDirectory: string | null | undefined;
session: string | null | undefined;
+ isDraggable?: boolean;
}
export const SessionUserHostWorkingDir = React.memo(
- ({ eventId, contextId, hostName, userName, primary, secondary, workingDirectory, session }) => (
+ ({
+ eventId,
+ contextId,
+ hostName,
+ userName,
+ primary,
+ secondary,
+ workingDirectory,
+ session,
+ isDraggable,
+ }) => (
<>
{i18n.SESSION}
@@ -38,6 +49,7 @@ export const SessionUserHostWorkingDir = React.memo(
field="auditd.session"
value={session}
iconType="number"
+ isDraggable={isDraggable}
/>
@@ -47,6 +59,7 @@ export const SessionUserHostWorkingDir = React.memo(
userName={userName}
primary={primary}
secondary={secondary}
+ isDraggable={isDraggable}
/>
{hostName != null && (
@@ -59,6 +72,7 @@ export const SessionUserHostWorkingDir = React.memo(
eventId={eventId}
workingDirectory={workingDirectory}
hostName={hostName}
+ isDraggable={isDraggable}
/>
>
)
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.tsx
index e2418334dfc80..8859c601ad56d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.tsx
@@ -26,6 +26,7 @@ export const Bytes = React.memo<{
isDraggable ? (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/indicator_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/indicator_details.tsx
index 11846632f740e..f2e4555147c50 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/indicator_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/indicator_details.tsx
@@ -26,6 +26,7 @@ interface IndicatorDetailsProps {
indicatorProvider: string | undefined;
indicatorReference: string | undefined;
indicatorType: string | undefined;
+ isDraggable?: boolean;
}
export const IndicatorDetails: React.FC = ({
@@ -35,6 +36,7 @@ export const IndicatorDetails: React.FC = ({
indicatorProvider,
indicatorReference,
indicatorType,
+ isDraggable,
}) => (
= ({
data-test-subj="threat-match-indicator-details-indicator-type"
eventId={eventId}
field={INDICATOR_MATCHED_TYPE}
+ isDraggable={isDraggable}
value={indicatorType}
/>
@@ -71,6 +74,7 @@ export const IndicatorDetails: React.FC = ({
data-test-subj="threat-match-indicator-details-indicator-dataset"
eventId={eventId}
field={INDICATOR_DATASET}
+ isDraggable={isDraggable}
value={indicatorDataset}
/>
@@ -92,6 +96,7 @@ export const IndicatorDetails: React.FC = ({
data-test-subj="threat-match-indicator-details-indicator-provider"
eventId={eventId}
field={INDICATOR_PROVIDER}
+ isDraggable={isDraggable}
value={indicatorProvider}
/>
@@ -108,6 +113,7 @@ export const IndicatorDetails: React.FC = ({
data-test-subj="threat-match-indicator-details-indicator-reference"
eventId={eventId}
fieldName={INDICATOR_REFERENCE}
+ isDraggable={isDraggable}
value={indicatorReference}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/match_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/match_details.tsx
index 2195421301d31..31c5065cde59a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/match_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/match_details.tsx
@@ -16,6 +16,7 @@ import { HorizontalSpacer } from './helpers';
interface MatchDetailsProps {
contextId: string;
eventId: string;
+ isDraggable?: boolean;
sourceField: string;
sourceValue: string;
}
@@ -23,6 +24,7 @@ interface MatchDetailsProps {
export const MatchDetails: React.FC = ({
contextId,
eventId,
+ isDraggable,
sourceField,
sourceValue,
}) => (
@@ -40,6 +42,7 @@ export const MatchDetails: React.FC = ({
data-test-subj="threat-match-details-source-field"
eventId={eventId}
field={INDICATOR_MATCHED_FIELD}
+ isDraggable={isDraggable}
value={sourceField}
/>
@@ -57,6 +60,7 @@ export const MatchDetails: React.FC = ({
data-test-subj="threat-match-details-source-value"
eventId={eventId}
field={sourceField}
+ isDraggable={isDraggable}
value={sourceValue}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.tsx
index ba5b0127df526..94ed19e218d74 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row.tsx
@@ -28,6 +28,7 @@ export interface ThreatMatchRowProps {
indicatorProvider: string | undefined;
indicatorReference: string | undefined;
indicatorType: string | undefined;
+ isDraggable?: boolean;
sourceField: string;
sourceValue: string;
}
@@ -36,10 +37,12 @@ export const ThreatMatchRow = ({
contextId,
data,
eventId,
+ isDraggable,
}: {
contextId: string;
data: Fields;
eventId: string;
+ isDraggable?: boolean;
}) => {
const props = {
contextId,
@@ -48,6 +51,7 @@ export const ThreatMatchRow = ({
indicatorReference: get(data, EVENT_REFERENCE)[0] as string | undefined,
indicatorProvider: get(data, PROVIDER)[0] as string | undefined,
indicatorType: get(data, MATCHED_TYPE)[0] as string | undefined,
+ isDraggable,
sourceField: get(data, MATCHED_FIELD)[0] as string,
sourceValue: get(data, MATCHED_ATOMIC)[0] as string,
};
@@ -62,6 +66,7 @@ export const ThreatMatchRowView = ({
indicatorProvider,
indicatorReference,
indicatorType,
+ isDraggable,
sourceField,
sourceValue,
}: ThreatMatchRowProps) => {
@@ -76,6 +81,7 @@ export const ThreatMatchRowView = ({
@@ -88,6 +94,7 @@ export const ThreatMatchRowView = ({
indicatorProvider={indicatorProvider}
indicatorReference={indicatorReference}
indicatorType={indicatorType}
+ isDraggable={isDraggable}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx
index 6687179e5b887..78972442f5018 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx
@@ -56,6 +56,7 @@ describe('threatMatchRowRenderer', () => {
const children = threatMatchRowRenderer.renderRow({
browserFields: {},
data: threatMatchData,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = shallow({children} );
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 f6feb6dd1b126..d2c1f09d903c1 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
@@ -20,7 +20,7 @@ const SpacedContainer = styled.div`
margin: ${({ theme }) => theme.eui.paddingSizes.s} 0;
`;
-export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, timelineId }) => {
+export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, isDraggable, timelineId }) => {
const indicators = get(data, 'threat.indicator') as Fields[];
const eventId = get(data, ID_FIELD_NAME);
@@ -31,7 +31,12 @@ export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, timelineId })
const contextId = `threat-match-row-${timelineId}-${eventId}-${index}`;
return (
-
+
{index < indicators.length - 1 && }
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.tsx
index 3a53db2196d8c..90d68eceb7fb3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.tsx
@@ -20,46 +20,50 @@ interface Props {
browserFields: BrowserFields;
contextId: string;
data: Ecs;
+ isDraggable?: boolean;
timelineId: string;
}
-export const DnsRequestEventDetails = React.memo(({ data, contextId, timelineId }) => {
- const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', data);
- const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', data);
- const dnsResolvedIp: string | null | undefined = get('dns.resolved_ip[0]', data);
- const dnsResponseCode: string | null | undefined = get('dns.response_code[0]', data);
- const eventCode: string | null | undefined = get('event.code[0]', data);
- const hostName: string | null | undefined = get('host.name[0]', data);
- const id = data._id;
- const processExecutable: string | null | undefined = get('process.executable[0]', data);
- const processName: string | null | undefined = get('process.name[0]', data);
- const processPid: number | null | undefined = get('process.pid[0]', data);
- const userDomain: string | null | undefined = get('user.domain[0]', data);
- const userName: string | null | undefined = get('user.name[0]', data);
- const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
+export const DnsRequestEventDetails = React.memo(
+ ({ data, contextId, isDraggable, timelineId }) => {
+ const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', data);
+ const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', data);
+ const dnsResolvedIp: string | null | undefined = get('dns.resolved_ip[0]', data);
+ const dnsResponseCode: string | null | undefined = get('dns.response_code[0]', data);
+ const eventCode: string | null | undefined = get('event.code[0]', data);
+ const hostName: string | null | undefined = get('host.name[0]', data);
+ const id = data._id;
+ const processExecutable: string | null | undefined = get('process.executable[0]', data);
+ const processName: string | null | undefined = get('process.name[0]', data);
+ const processPid: number | null | undefined = get('process.pid[0]', data);
+ const userDomain: string | null | undefined = get('user.domain[0]', data);
+ const userName: string | null | undefined = get('user.name[0]', data);
+ const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
- return (
-
-
-
-
-
- );
-});
+ return (
+
+
+
+
+
+ );
+ }
+);
DnsRequestEventDetails.displayName = 'DnsRequestEventDetails';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx
index 549abcf6a6d35..ff85336bd47f8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx
@@ -24,6 +24,7 @@ interface Props {
eventCode: string | null | undefined;
hostName: string | null | undefined;
id: string;
+ isDraggable?: boolean;
processExecutable: string | null | undefined;
processName: string | null | undefined;
processPid: number | null | undefined;
@@ -42,6 +43,7 @@ export const DnsRequestEventDetailsLine = React.memo(
eventCode,
hostName,
id,
+ isDraggable,
processExecutable,
processName,
processPid,
@@ -56,6 +58,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
hostName={hostName}
+ isDraggable={isDraggable}
userDomain={userDomain}
userName={userName}
workingDirectory={undefined}
@@ -71,6 +74,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="dns.question.name"
+ isDraggable={isDraggable}
value={dnsQuestionName}
/>
@@ -87,6 +91,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="dns.question.type"
+ isDraggable={isDraggable}
value={dnsQuestionType}
/>
@@ -103,6 +108,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="dns.resolved_ip"
+ isDraggable={isDraggable}
value={dnsResolvedIp}
/>
@@ -122,6 +128,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="dns.response_code"
+ isDraggable={isDraggable}
value={dnsResponseCode}
/>
@@ -141,6 +148,7 @@ export const DnsRequestEventDetailsLine = React.memo(
endgamePid={undefined}
endgameProcessName={undefined}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
@@ -155,6 +163,7 @@ export const DnsRequestEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="event.code"
+ isDraggable={isDraggable}
value={eventCode}
/>
@@ -165,6 +174,7 @@ export const DnsRequestEventDetailsLine = React.memo(
eventId={id}
iconType="logoWindows"
field="winlog.event_id"
+ isDraggable={isDraggable}
value={winlogEventId}
/>
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 8e2335a2f149b..db568726f1b20 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
@@ -60,6 +60,7 @@ export const emptyColumnRenderer: ColumnRenderer = {
kqlQuery: '',
and: [],
}}
+ isDraggable={isDraggable}
key={`empty-column-renderer-draggable-wrapper-${timelineId}-${columnName}-${eventId}-${field.id}`}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx
index 515db45e9fcd4..8f39cf933570f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx
@@ -20,65 +20,75 @@ interface Props {
browserFields: BrowserFields;
contextId: string;
data: Ecs;
+ isDraggable?: boolean;
timelineId: string;
}
-export const EndgameSecurityEventDetails = React.memo(({ data, contextId, timelineId }) => {
- const endgameLogonType: number | null | undefined = get('endgame.logon_type[0]', data);
- const endgameSubjectDomainName: string | null | undefined = get(
- 'endgame.subject_domain_name[0]',
- data
- );
- const endgameSubjectLogonId: string | null | undefined = get('endgame.subject_logon_id[0]', data);
- const endgameSubjectUserName: string | null | undefined = get(
- 'endgame.subject_user_name[0]',
- data
- );
- const endgameTargetLogonId: string | null | undefined = get('endgame.target_logon_id[0]', data);
- const endgameTargetDomainName: string | null | undefined = get(
- 'endgame.target_domain_name[0]',
- data
- );
- const endgameTargetUserName: string | null | undefined = get('endgame.target_user_name[0]', data);
- const eventAction: string | null | undefined = get('event.action[0]', data);
- const eventCode: string | null | undefined = get('event.code[0]', data);
- const eventOutcome: string | null | undefined = get('event.outcome[0]', data);
- const hostName: string | null | undefined = get('host.name[0]', data);
- const id = data._id;
- const processExecutable: string | null | undefined = get('process.executable[0]', data);
- const processName: string | null | undefined = get('process.name[0]', data);
- const processPid: number | null | undefined = get('process.pid[0]', data);
- const userDomain: string | null | undefined = get('user.domain[0]', data);
- const userName: string | null | undefined = get('user.name[0]', data);
- const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
+export const EndgameSecurityEventDetails = React.memo(
+ ({ data, contextId, isDraggable, timelineId }) => {
+ const endgameLogonType: number | null | undefined = get('endgame.logon_type[0]', data);
+ const endgameSubjectDomainName: string | null | undefined = get(
+ 'endgame.subject_domain_name[0]',
+ data
+ );
+ const endgameSubjectLogonId: string | null | undefined = get(
+ 'endgame.subject_logon_id[0]',
+ data
+ );
+ const endgameSubjectUserName: string | null | undefined = get(
+ 'endgame.subject_user_name[0]',
+ data
+ );
+ const endgameTargetLogonId: string | null | undefined = get('endgame.target_logon_id[0]', data);
+ const endgameTargetDomainName: string | null | undefined = get(
+ 'endgame.target_domain_name[0]',
+ data
+ );
+ const endgameTargetUserName: string | null | undefined = get(
+ 'endgame.target_user_name[0]',
+ data
+ );
+ const eventAction: string | null | undefined = get('event.action[0]', data);
+ const eventCode: string | null | undefined = get('event.code[0]', data);
+ const eventOutcome: string | null | undefined = get('event.outcome[0]', data);
+ const hostName: string | null | undefined = get('host.name[0]', data);
+ const id = data._id;
+ const processExecutable: string | null | undefined = get('process.executable[0]', data);
+ const processName: string | null | undefined = get('process.name[0]', data);
+ const processPid: number | null | undefined = get('process.pid[0]', data);
+ const userDomain: string | null | undefined = get('user.domain[0]', data);
+ const userName: string | null | undefined = get('user.name[0]', data);
+ const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
- return (
-
-
-
-
-
- );
-});
+ return (
+
+
+
+
+
+ );
+ }
+);
EndgameSecurityEventDetails.displayName = 'EndgameSecurityEventDetails';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx
index aba6f7346271d..7e5a6dd08765b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx
@@ -38,6 +38,7 @@ interface Props {
eventOutcome: string | null | undefined;
hostName: string | null | undefined;
id: string;
+ isDraggable?: boolean;
processExecutable: string | null | undefined;
processName: string | null | undefined;
processPid: number | null | undefined;
@@ -61,6 +62,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
eventOutcome,
hostName,
id,
+ isDraggable,
processExecutable,
processName,
processPid,
@@ -95,6 +97,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
eventId={id}
hostName={hostName}
hostNameSeparator={hostNameSeparator}
+ isDraggable={isDraggable}
userDomain={domain}
userDomainField={userDomainField}
userName={user}
@@ -116,6 +119,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="endgame.logon_type"
+ isDraggable={isDraggable}
queryValue={String(endgameLogonType)}
value={`${endgameLogonType} - ${getHumanReadableLogonType(endgameLogonType)}`}
/>
@@ -136,6 +140,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="endgame.target_logon_id"
+ isDraggable={isDraggable}
value={endgameTargetLogonId}
/>
@@ -155,6 +160,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
endgamePid={undefined}
endgameProcessName={undefined}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
@@ -176,6 +182,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="endgame.subject_user_name"
+ isDraggable={isDraggable}
iconType="user"
value={endgameSubjectUserName}
/>
@@ -197,6 +204,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="endgame.subject_domain_name"
+ isDraggable={isDraggable}
value={endgameSubjectDomainName}
/>
@@ -216,6 +224,7 @@ export const EndgameSecurityEventDetailsLine = React.memo(
contextId={contextId}
eventId={id}
field="endgame.subject_logon_id"
+ isDraggable={isDraggable}
value={endgameSubjectLogonId}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.tsx
index 7ac9fe290893f..1f1862daa4e55 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.tsx
@@ -15,12 +15,13 @@ interface Props {
contextId: string;
endgameExitCode: string | null | undefined;
eventId: string;
+ isDraggable?: boolean;
processExitCode: number | null | undefined;
text: string | null | undefined;
}
export const ExitCodeDraggable = React.memo(
- ({ contextId, endgameExitCode, eventId, processExitCode, text }) => {
+ ({ contextId, endgameExitCode, eventId, isDraggable, processExitCode, text }) => {
if (isNillEmptyOrNotFinite(processExitCode) && isNillEmptyOrNotFinite(endgameExitCode)) {
return null;
}
@@ -39,6 +40,7 @@ export const ExitCodeDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="process.exit_code"
+ isDraggable={isDraggable}
value={`${processExitCode}`}
/>
@@ -50,6 +52,7 @@ export const ExitCodeDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="endgame.exit_code"
+ isDraggable={isDraggable}
value={endgameExitCode}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
index 703b38e627e55..7ff5a0f73ab30 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
@@ -20,6 +20,7 @@ interface Props {
fileName: string | null | undefined;
filePath: string | null | undefined;
fileExtOriginalPath: string | null | undefined;
+ isDraggable?: boolean;
}
export const FileDraggable = React.memo(
@@ -31,6 +32,7 @@ export const FileDraggable = React.memo(
fileExtOriginalPath,
fileName,
filePath,
+ isDraggable,
}) => {
if (
isNillEmptyOrNotFinite(fileName) &&
@@ -52,6 +54,7 @@ export const FileDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="file.name"
+ isDraggable={isDraggable}
value={fileName}
iconType="document"
/>
@@ -62,6 +65,7 @@ export const FileDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="endgame.file_name"
+ isDraggable={isDraggable}
value={endgameFileName}
iconType="document"
/>
@@ -80,6 +84,7 @@ export const FileDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="file.path"
+ isDraggable={isDraggable}
value={filePath}
iconType="document"
/>
@@ -90,6 +95,7 @@ export const FileDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="endgame.file_path"
+ isDraggable={isDraggable}
value={endgameFilePath}
iconType="document"
/>
@@ -106,6 +112,7 @@ export const FileDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="file.Ext.original.path"
+ isDraggable={isDraggable}
value={fileExtOriginalPath}
iconType="document"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
index 9e624ba17c921..13b024e0a5359 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
@@ -21,9 +21,10 @@ interface Props {
contextId: string;
eventId: string;
fileHashSha256: string | null | undefined;
+ isDraggable?: boolean;
}
-export const FileHash = React.memo(({ contextId, eventId, fileHashSha256 }) => {
+export const FileHash = React.memo(({ contextId, eventId, fileHashSha256, isDraggable }) => {
if (isNillEmptyOrNotFinite(fileHashSha256)) {
return null;
}
@@ -35,6 +36,7 @@ export const FileHash = React.memo(({ contextId, eventId, fileHashSha256
contextId={contextId}
eventId={eventId}
field="file.hash.sha256"
+ isDraggable={isDraggable}
iconType="number"
value={fileHashSha256}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
index aa6c7beb9139e..06ed901110962 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx
@@ -86,6 +86,7 @@ const FormattedFieldValueComponent: React.FC<{
@@ -214,6 +215,7 @@ const FormattedFieldValueComponent: React.FC<{
= ({
@@ -95,6 +96,7 @@ export const RenderRuleName: React.FC = ({
@@ -150,6 +152,7 @@ export const renderEventModule = ({
@@ -218,6 +221,7 @@ export const renderUrl = ({
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 104550f138f16..6b76aba92678d 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
@@ -54,6 +54,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: nonSuricata,
+ isDraggable: true,
timelineId: 'test',
});
@@ -66,6 +67,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: nonSuricata,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -81,6 +83,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: suricata,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -99,6 +102,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: suricata,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -117,6 +121,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: zeek,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -135,6 +140,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: system,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -153,6 +159,7 @@ describe('get_column_renderer', () => {
const row = rowRenderer?.renderRow({
browserFields: mockBrowserFields,
data: auditd,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx
index abd4731ec4b66..060b539950d83 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx
@@ -93,6 +93,7 @@ const HostNameComponent: React.FC = ({
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.tsx
index de307d1af7f93..fef9a5d5c0201 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.tsx
@@ -17,10 +17,11 @@ interface Props {
eventId: string;
hostName: string | null | undefined;
workingDirectory: string | null | undefined;
+ isDraggable?: boolean;
}
export const HostWorkingDir = React.memo(
- ({ contextId, eventId, hostName, workingDirectory }) => (
+ ({ contextId, eventId, hostName, workingDirectory, isDraggable }) => (
<>
(
eventId={eventId}
field="host.name"
value={hostName}
+ isDraggable={isDraggable}
/>
{workingDirectory != null && (
@@ -42,6 +44,7 @@ export const HostWorkingDir = React.memo(
field="process.working_directory"
value={workingDirectory}
iconType="folderOpen"
+ isDraggable={isDraggable}
/>
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow.tsx
index 18f56d8b03066..d6ea939c966ac 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow.tsx
@@ -60,52 +60,58 @@ import {
interface NetflowRendererProps {
data: Ecs;
timelineId: string;
+ isDraggable?: boolean;
}
-export const NetflowRenderer = React.memo(({ data, timelineId }) => (
-
-));
+export const NetflowRenderer = React.memo(
+ ({ data, timelineId, isDraggable }) => (
+
+ )
+);
NetflowRenderer.displayName = 'NetflowRenderer';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap
index d7bdacbcc61ef..a9ecbe8428aee 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap
@@ -67,6 +67,7 @@ exports[`netflowRowRenderer renders correctly against snapshot 1`] = `
"2018-11-12T19:03:25.836Z",
]
}
+ isDraggable={true}
networkBytes={
Array [
100,
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 fc97624dbfc96..01e05bbc365e9 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
@@ -38,6 +38,7 @@ describe('netflowRowRenderer', () => {
const children = netflowRowRenderer.renderRow({
browserFields,
data: getMockNetflowData(),
+ isDraggable: true,
timelineId: 'test',
});
@@ -107,6 +108,7 @@ describe('netflowRowRenderer', () => {
const children = netflowRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: getMockNetflowData(),
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
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 35406dce6ff72..272912b855af0 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
@@ -90,7 +90,7 @@ export const netflowRowRenderer: RowRenderer = {
isInstance: (ecs) =>
eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) ||
eventActionMatches(get(EVENT_ACTION_FIELD, ecs)),
- renderRow: ({ data, timelineId }) => (
+ renderRow: ({ data, isDraggable, timelineId }) => (
(
contextId,
endgameParentProcessName,
eventId,
+ isDraggable,
processParentName,
processParentPid,
processPpid,
@@ -56,6 +58,7 @@ export const ParentProcessDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="process.parent.name"
+ isDraggable={isDraggable}
value={processParentName}
/>
@@ -67,6 +70,7 @@ export const ParentProcessDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="endgame.parent_process_name"
+ isDraggable={isDraggable}
value={endgameParentProcessName}
/>
@@ -78,6 +82,7 @@ export const ParentProcessDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="process.parent.pid"
+ isDraggable={isDraggable}
queryValue={String(processParentPid)}
value={`(${String(processParentPid)})`}
/>
@@ -90,6 +95,7 @@ export const ParentProcessDraggable = React.memo(
contextId={contextId}
eventId={eventId}
field="process.ppid"
+ isDraggable={isDraggable}
queryValue={String(processPpid)}
value={`(${String(processPpid)})`}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.test.tsx
index 666fb254aaa2c..f28c72253a4e7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.test.tsx
@@ -24,6 +24,7 @@ describe('plain_row_renderer', () => {
const children = plainRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockDatum,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = shallow({children} );
@@ -38,6 +39,7 @@ describe('plain_row_renderer', () => {
const children = plainRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockDatum,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount({children} );
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.tsx
index 705eff8873204..db7e3ae6f06c9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.tsx
@@ -21,6 +21,7 @@ interface Props {
processExecutable: string | undefined | null;
processPid: number | undefined | null;
processName: string | undefined | null;
+ isDraggable?: boolean;
}
export const ProcessDraggable = React.memo(
@@ -32,6 +33,7 @@ export const ProcessDraggable = React.memo(
processExecutable,
processName,
processPid,
+ isDraggable,
}) => {
if (
isNillEmptyOrNotFinite(processName) &&
@@ -53,6 +55,7 @@ export const ProcessDraggable = React.memo(
field="process.name"
value={processName}
iconType="console"
+ isDraggable={isDraggable}
/>
) : !isNillEmptyOrNotFinite(processExecutable) ? (
@@ -63,6 +66,7 @@ export const ProcessDraggable = React.memo(
field="process.executable"
value={processExecutable}
iconType="console"
+ isDraggable={isDraggable}
/>
) : !isNillEmptyOrNotFinite(endgameProcessName) ? (
@@ -73,6 +77,7 @@ export const ProcessDraggable = React.memo(
field="endgame.process_name"
value={endgameProcessName}
iconType="console"
+ isDraggable={isDraggable}
/>
) : null}
@@ -85,6 +90,7 @@ export const ProcessDraggable = React.memo(
field="process.pid"
queryValue={String(processPid)}
value={`(${String(processPid)})`}
+ isDraggable={isDraggable}
/>
) : !isNillEmptyOrNotFinite(endgamePid) ? (
@@ -95,6 +101,7 @@ export const ProcessDraggable = React.memo(
field="endgame.pid"
queryValue={String(endgamePid)}
value={`(${String(endgamePid)})`}
+ isDraggable={isDraggable}
/>
) : null}
@@ -114,6 +121,7 @@ export const ProcessDraggableWithNonExistentProcess = React.memo(
processExecutable,
processName,
processPid,
+ isDraggable,
}) => {
if (
endgamePid == null &&
@@ -133,6 +141,7 @@ export const ProcessDraggableWithNonExistentProcess = React.memo(
processExecutable={processExecutable}
processName={processName}
processPid={processPid}
+ isDraggable={isDraggable}
/>
);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
index 32432afbf205c..dd4f588a14bb7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
@@ -20,27 +20,31 @@ const HashFlexGroup = styled(EuiFlexGroup)`
interface Props {
contextId: string;
eventId: string;
+ isDraggable?: boolean;
processHashSha256: string | null | undefined;
}
-export const ProcessHash = React.memo(({ contextId, eventId, processHashSha256 }) => {
- if (isNillEmptyOrNotFinite(processHashSha256)) {
- return null;
+export const ProcessHash = React.memo(
+ ({ contextId, eventId, isDraggable, processHashSha256 }) => {
+ if (isNillEmptyOrNotFinite(processHashSha256)) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
}
-
- return (
-
-
-
-
-
- );
-});
+);
ProcessHash.displayName = 'ProcessHash';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
index 0bfb03168019a..da31f75e2fa10 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
@@ -18,10 +18,11 @@ interface Props {
browserFields: BrowserFields;
contextId: string;
data: Ecs;
+ isDraggable?: boolean;
text: string;
}
-const RegistryEventDetailsComponent: React.FC = ({ contextId, data, text }) => {
+const RegistryEventDetailsComponent: React.FC = ({ contextId, data, isDraggable, text }) => {
const hostName: string | null | undefined = get('host.name[0]', data);
const id = data._id;
const processName: string | null | undefined = get('process.name[0]', data);
@@ -41,6 +42,7 @@ const RegistryEventDetailsComponent: React.FC = ({ contextId, data, text
contextId={contextId}
hostName={hostName}
id={id}
+ isDraggable={isDraggable}
processName={processName}
processPid={processPid}
registryKey={registryKey}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
index b85ae25ed2509..8d9f52da88fdd 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
@@ -19,6 +19,7 @@ interface Props {
contextId: string;
hostName: string | null | undefined;
id: string;
+ isDraggable?: boolean;
processName: string | null | undefined;
processPid: number | null | undefined;
registryKey: string | null | undefined;
@@ -32,6 +33,7 @@ const RegistryEventDetailsLineComponent: React.FC = ({
contextId,
hostName,
id,
+ isDraggable,
processName,
processPid,
registryKey,
@@ -71,6 +73,7 @@ const RegistryEventDetailsLineComponent: React.FC = ({
contextId={contextId}
eventId={id}
hostName={hostName}
+ isDraggable={isDraggable}
userDomain={userDomain}
userName={userName}
workingDirectory={undefined}
@@ -86,6 +89,7 @@ const RegistryEventDetailsLineComponent: React.FC = ({
contextId={contextId}
eventId={id}
field="registry.key"
+ isDraggable={isDraggable}
tooltipContent={registryKeyTooltipContent}
value={registryKey}
/>
@@ -103,6 +107,7 @@ const RegistryEventDetailsLineComponent: React.FC = ({
contextId={contextId}
eventId={id}
field="registry.path"
+ isDraggable={isDraggable}
tooltipContent={registryPathTooltipContent}
value={registryPath}
/>
@@ -120,6 +125,7 @@ const RegistryEventDetailsLineComponent: React.FC = ({
endgamePid={undefined}
endgameProcessName={undefined}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={undefined}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx
index 126bfae996ef7..09248b832490a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx
@@ -42,6 +42,7 @@ const RuleStatusComponent: React.FC = ({
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
index 2934d35dc184d..eeb8786b2cfa3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
@@ -525,6 +525,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = `
},
}
}
+ isDraggable={true}
timelineId="test"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.tsx
index 82eb11d455543..f096cd906f619 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.tsx
@@ -26,8 +26,9 @@ Details.displayName = 'Details';
export const SuricataDetails = React.memo<{
browserFields: BrowserFields;
data: Ecs;
+ isDraggable?: boolean;
timelineId: string;
-}>(({ data, timelineId }) => {
+}>(({ data, isDraggable, timelineId }) => {
const signature: string | null | undefined = get('suricata.eve.alert.signature[0]', data);
const signatureId: number | null | undefined = get('suricata.eve.alert.signature_id[0]', data);
@@ -37,12 +38,13 @@ export const SuricataDetails = React.memo<{
-
+
);
} else {
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 998233b2278c9..661fc562cc34c 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
@@ -45,6 +45,7 @@ describe('suricata_row_renderer', () => {
const children = suricataRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: nonSuricata,
+ isDraggable: true,
timelineId: 'test',
});
@@ -64,6 +65,7 @@ describe('suricata_row_renderer', () => {
const children = suricataRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: suricata,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -81,6 +83,7 @@ describe('suricata_row_renderer', () => {
const children = suricataRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: suricata,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
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 aa482926bf007..0faa6a4fbba74 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
@@ -21,9 +21,14 @@ export const suricataRowRenderer: RowRenderer = {
const module: string | null | undefined = get('event.module[0]', ecs);
return module != null && module.toLowerCase() === 'suricata';
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
-
+
),
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
index a4e16c66f4fef..2a5b57d77498f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
@@ -57,65 +57,69 @@ export const Tokens = React.memo<{ tokens: string[] }>(({ tokens }) => (
Tokens.displayName = 'Tokens';
-export const DraggableSignatureId = React.memo<{ id: string; signatureId: number }>(
- ({ id, signatureId }) => {
- const dataProviderProp = useMemo(
- () => ({
- and: [],
- enabled: true,
- id: escapeDataProviderId(`suricata-draggable-signature-id-${id}-sig-${signatureId}`),
- name: String(signatureId),
- excluded: false,
- kqlQuery: '',
- queryMatch: {
- field: SURICATA_SIGNATURE_ID_FIELD_NAME,
- value: signatureId,
- operator: IS_OPERATOR as QueryOperator,
- },
- }),
- [id, signatureId]
- );
-
- const render = useCallback(
- (dataProvider, _, snapshot) =>
- snapshot.isDragging ? (
-
-
-
- ) : (
-
-
- {signatureId}
-
-
- ),
- [signatureId]
- );
-
- return (
-
-
-
- );
- }
-);
+export const DraggableSignatureId = React.memo<{
+ id: string;
+ isDraggable?: boolean;
+ signatureId: number;
+}>(({ id, isDraggable, signatureId }) => {
+ const dataProviderProp = useMemo(
+ () => ({
+ and: [],
+ enabled: true,
+ id: escapeDataProviderId(`suricata-draggable-signature-id-${id}-sig-${signatureId}`),
+ name: String(signatureId),
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field: SURICATA_SIGNATURE_ID_FIELD_NAME,
+ value: signatureId,
+ operator: IS_OPERATOR as QueryOperator,
+ },
+ }),
+ [id, signatureId]
+ );
+
+ const render = useCallback(
+ (dataProvider, _, snapshot) =>
+ snapshot.isDragging ? (
+
+
+
+ ) : (
+
+
+ {signatureId}
+
+
+ ),
+ [signatureId]
+ );
+
+ return (
+
+
+
+ );
+});
DraggableSignatureId.displayName = 'DraggableSignatureId';
export const SuricataSignature = React.memo<{
contextId: string;
id: string;
+ isDraggable?: boolean;
signature: string;
signatureId: number;
-}>(({ contextId, id, signature, signatureId }) => {
+}>(({ contextId, id, isDraggable, signature, signatureId }) => {
const tokens = getBeginningTokens(signature);
return (
@@ -124,6 +128,7 @@ export const SuricataSignature = React.memo<{
data-test-subj="draggable-signature-link"
field={SURICATA_SIGNATURE_FIELD_NAME}
id={`suricata-signature-default-draggable-${contextId}-${id}-${SURICATA_SIGNATURE_FIELD_NAME}`}
+ isDraggable={isDraggable}
value={signature}
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap
index d2c405a46acf8..15443058f434e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_row_renderer.test.tsx.snap
@@ -56,6 +56,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
},
}
}
+ isDraggable={true}
text="some text"
timelineId="test"
/>
@@ -119,6 +120,7 @@ exports[`GenericRowRenderer #createGenericSystemRowRenderer renders correctly ag
},
}
}
+ isDraggable={true}
text="some text"
timelineId="test"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.tsx
index 4dcb90637a817..7de03a2ae2356 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.tsx
@@ -13,35 +13,40 @@ import { TokensFlexItem } from '../helpers';
interface Props {
contextId: string;
eventId: string;
+ isDraggable?: boolean;
sshSignature: string | null | undefined;
sshMethod: string | null | undefined;
}
-export const AuthSsh = React.memo
(({ contextId, eventId, sshSignature, sshMethod }) => (
- <>
- {sshSignature != null && (
-
-
-
- )}
- {sshMethod != null && (
-
-
-
- )}
- >
-));
+export const AuthSsh = React.memo(
+ ({ contextId, eventId, isDraggable, sshSignature, sshMethod }) => (
+ <>
+ {sshSignature != null && (
+
+
+
+ )}
+ {sshMethod != null && (
+
+
+
+ )}
+ >
+ )
+);
AuthSsh.displayName = 'AuthSsh';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.tsx
index 19d5fd2a0dab1..2cf42ecc9c670 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.tsx
@@ -27,6 +27,7 @@ interface Props {
contextId: string;
hostName: string | null | undefined;
id: string;
+ isDraggable?: boolean;
message: string | null | undefined;
outcome: string | null | undefined;
packageName: string | null | undefined;
@@ -48,6 +49,7 @@ export const SystemGenericLine = React.memo(
contextId,
hostName,
id,
+ isDraggable,
message,
outcome,
packageName,
@@ -68,9 +70,10 @@ export const SystemGenericLine = React.memo(
@@ -82,6 +85,7 @@ export const SystemGenericLine = React.memo(
endgamePid={undefined}
endgameProcessName={undefined}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
@@ -97,6 +101,7 @@ export const SystemGenericLine = React.memo(
contextId={contextId}
eventId={id}
field="event.outcome"
+ isDraggable={isDraggable}
queryValue={outcome}
value={outcome}
/>
@@ -104,12 +109,14 @@ export const SystemGenericLine = React.memo(
(
- ({ data, contextId, text, timelineId }) => {
+ ({ contextId, data, isDraggable, text, timelineId }) => {
const id = data._id;
const message: string | null = data.message != null ? data.message[0] : null;
const hostName: string | null | undefined = get('host.name[0]', data);
@@ -165,6 +173,7 @@ export const SystemGenericDetails = React.memo(
contextId={contextId}
hostName={hostName}
id={id}
+ isDraggable={isDraggable}
message={message}
outcome={outcome}
packageName={packageName}
@@ -181,7 +190,7 @@ export const SystemGenericDetails = React.memo(
workingDirectory={workingDirectory}
/>
-
+
);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
index 6df583656ff2d..ae31dbff7f063 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
@@ -45,6 +45,7 @@ interface Props {
filePath: string | null | undefined;
hostName: string | null | undefined;
id: string;
+ isDraggable?: boolean;
message: string | null | undefined;
outcome: string | null | undefined;
packageName: string | null | undefined;
@@ -87,6 +88,7 @@ export const SystemGenericFileLine = React.memo(
filePath,
hostName,
id,
+ isDraggable,
message,
outcome,
packageName,
@@ -116,6 +118,7 @@ export const SystemGenericFileLine = React.memo(
(
fileExtOriginalPath={fileExtOriginalPath}
fileName={fileName}
filePath={filePath}
+ isDraggable={isDraggable}
/>
)}
{showVia(eventAction) && (
@@ -147,6 +151,7 @@ export const SystemGenericFileLine = React.memo(
endgamePid={endgamePid}
endgameProcessName={endgameProcessName}
eventId={id}
+ isDraggable={isDraggable}
processPid={processPid}
processName={processName}
processExecutable={processExecutable}
@@ -157,6 +162,7 @@ export const SystemGenericFileLine = React.memo(
contextId={contextId}
endgameExitCode={endgameExitCode}
eventId={id}
+ isDraggable={isDraggable}
processExitCode={processExitCode}
text={i18n.WITH_EXIT_CODE}
/>
@@ -165,6 +171,7 @@ export const SystemGenericFileLine = React.memo(
contextId={contextId}
endgameParentProcessName={endgameParentProcessName}
eventId={id}
+ isDraggable={isDraggable}
processParentName={processParentName}
processParentPid={processParentPid}
processPpid={processPpid}
@@ -181,6 +188,7 @@ export const SystemGenericFileLine = React.memo(
contextId={contextId}
eventId={id}
field="event.outcome"
+ isDraggable={isDraggable}
queryValue={outcome}
value={outcome}
/>
@@ -188,22 +196,34 @@ export const SystemGenericFileLine = React.memo(
{!skipRedundantFileDetails && (
-
+
)}
{!skipRedundantProcessDetails && (
-
+
)}
{message != null && showMessage && (
@@ -226,8 +246,9 @@ SystemGenericFileLine.displayName = 'SystemGenericFileLine';
interface GenericDetailsProps {
browserFields: BrowserFields;
- data: Ecs;
contextId: string;
+ data: Ecs;
+ isDraggable?: boolean;
showMessage?: boolean;
skipRedundantFileDetails?: boolean;
skipRedundantProcessDetails?: boolean;
@@ -237,8 +258,9 @@ interface GenericDetailsProps {
export const SystemGenericFileDetails = React.memo(
({
- data,
contextId,
+ data,
+ isDraggable,
showMessage = true,
skipRedundantFileDetails = false,
skipRedundantProcessDetails = false,
@@ -323,9 +345,10 @@ export const SystemGenericFileDetails = React.memo(
sshSignature={sshSignature}
sshMethod={sshMethod}
outcome={outcome}
+ isDraggable={isDraggable}
/>
-
+
);
}
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 6f5b225f0690b..516d279765904 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
@@ -118,6 +118,7 @@ describe('GenericRowRenderer', () => {
const children = connectedToRenderer.renderRow({
browserFields,
data: system,
+ isDraggable: true,
timelineId: 'test',
});
@@ -147,6 +148,7 @@ describe('GenericRowRenderer', () => {
const children = connectedToRenderer.renderRow({
browserFields: mockBrowserFields,
data: system,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -180,6 +182,7 @@ describe('GenericRowRenderer', () => {
const children = fileToRenderer.renderRow({
browserFields,
data: systemFile,
+ isDraggable: true,
timelineId: 'test',
});
@@ -208,6 +211,7 @@ describe('GenericRowRenderer', () => {
const children = fileToRenderer.renderRow({
browserFields: mockBrowserFields,
data: systemFile,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
@@ -239,6 +243,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileCreationMalwarePreventionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -266,6 +271,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileCreationMalwareDetectionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -295,6 +301,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFilesEncryptedRansomwarePreventionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -324,6 +331,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFilesEncryptedRansomwareDetectionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -353,6 +361,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileModificationMalwarePreventionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -382,6 +391,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileModificationMalwareDetectionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -409,6 +419,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileRenameMalwarePreventionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -436,6 +447,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileRenameMalwareDetectionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -465,6 +477,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -494,6 +507,7 @@ describe('GenericRowRenderer', () => {
endpointAlertsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessExecutionMalwareDetectionAlert,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -521,6 +535,7 @@ describe('GenericRowRenderer', () => {
endpointProcessStartRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessExecEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -546,6 +561,7 @@ describe('GenericRowRenderer', () => {
endpointProcessStartRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessForkEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -571,6 +587,7 @@ describe('GenericRowRenderer', () => {
endpointProcessStartRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessStartEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -599,6 +616,7 @@ describe('GenericRowRenderer', () => {
endgameProcessCreationEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameCreationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -624,6 +642,7 @@ describe('GenericRowRenderer', () => {
endpointProcessEndRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointProcessEndEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -652,6 +671,7 @@ describe('GenericRowRenderer', () => {
endgameProcessTerminationEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameTerminationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -680,6 +700,7 @@ describe('GenericRowRenderer', () => {
endgameProcessCreationEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameCreationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -710,6 +731,7 @@ describe('GenericRowRenderer', () => {
endgameProcessCreationEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameCreationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -740,6 +762,7 @@ describe('GenericRowRenderer', () => {
endgameProcessCreationEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameCreationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -765,6 +788,7 @@ describe('GenericRowRenderer', () => {
endpointFileCreationRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileCreationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -793,6 +817,7 @@ describe('GenericRowRenderer', () => {
endgameFileCreateEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameFileCreateEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -818,6 +843,7 @@ describe('GenericRowRenderer', () => {
endpointFileDeletionRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileDeletionEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -843,6 +869,7 @@ describe('GenericRowRenderer', () => {
endpointFileModificationRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileModificationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -868,6 +895,7 @@ describe('GenericRowRenderer', () => {
endpointFileOverwriteRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileOverwriteEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -893,6 +921,7 @@ describe('GenericRowRenderer', () => {
endpointFileRenameRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointFileRenameEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -921,6 +950,7 @@ describe('GenericRowRenderer', () => {
endgameFileDeleteEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameFileDeleteEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -949,6 +979,7 @@ describe('GenericRowRenderer', () => {
fileCreatedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: fimFileCreatedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -975,6 +1006,7 @@ describe('GenericRowRenderer', () => {
fileDeletedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: fimFileDeletedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1003,6 +1035,7 @@ describe('GenericRowRenderer', () => {
endgameFileCreateEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameFileCreateEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1033,6 +1066,7 @@ describe('GenericRowRenderer', () => {
endgameFileCreateEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: endgameFileCreateEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1063,6 +1097,7 @@ describe('GenericRowRenderer', () => {
fileCreatedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: fimFileCreatedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1090,6 +1125,7 @@ describe('GenericRowRenderer', () => {
endpointConnectionAcceptedRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointNetworkConnectionAcceptedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1118,6 +1154,7 @@ describe('GenericRowRenderer', () => {
endpointRegistryModificationRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointRegistryModificationEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1145,6 +1182,7 @@ describe('GenericRowRenderer', () => {
endpointLibraryLoadRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointLibraryLoadEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1171,6 +1209,7 @@ describe('GenericRowRenderer', () => {
endpointHttpRequestEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointNetworkHttpRequestEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1199,6 +1238,7 @@ describe('GenericRowRenderer', () => {
endgameIpv4ConnectionAcceptEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: ipv4ConnectionAcceptEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1227,6 +1267,7 @@ describe('GenericRowRenderer', () => {
endgameIpv6ConnectionAcceptEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: ipv6ConnectionAcceptEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1252,6 +1293,7 @@ describe('GenericRowRenderer', () => {
endpointDisconnectReceivedRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointDisconnectReceivedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1280,6 +1322,7 @@ describe('GenericRowRenderer', () => {
endgameIpv4DisconnectReceivedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: ipv4DisconnectReceivedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1308,6 +1351,7 @@ describe('GenericRowRenderer', () => {
endgameIpv6DisconnectReceivedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: ipv6DisconnectReceivedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1336,6 +1380,7 @@ describe('GenericRowRenderer', () => {
socketOpenedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: socketOpenedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1364,6 +1409,7 @@ describe('GenericRowRenderer', () => {
socketClosedEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: socketClosedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1392,6 +1438,7 @@ describe('GenericRowRenderer', () => {
endgameIpv4ConnectionAcceptEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: ipv4ConnectionAcceptEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1413,6 +1460,7 @@ describe('GenericRowRenderer', () => {
securityLogOnRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointSecurityLogOnSuccessEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1434,6 +1482,7 @@ describe('GenericRowRenderer', () => {
securityLogOnRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointSecurityLogOnFailureEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1458,6 +1507,7 @@ describe('GenericRowRenderer', () => {
userLogonEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: userLogonEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1482,6 +1532,7 @@ describe('GenericRowRenderer', () => {
adminLogonEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: adminLogonEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1506,6 +1557,7 @@ describe('GenericRowRenderer', () => {
explicitUserLogonEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: explicitUserLogonEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1527,6 +1579,7 @@ describe('GenericRowRenderer', () => {
securityLogOffRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointSecurityLogOffEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1551,6 +1604,7 @@ describe('GenericRowRenderer', () => {
userLogoffEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: userLogoffEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1575,6 +1629,7 @@ describe('GenericRowRenderer', () => {
userLogonEventRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: userLogonEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1594,6 +1649,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointNetworkLookupRequestedEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1613,6 +1669,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockEndpointNetworkLookupResultEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1636,6 +1693,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: requestEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1659,6 +1717,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: dnsEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1688,6 +1747,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: requestEvent,
+ isDraggable: true,
timelineId: 'test',
})}
@@ -1715,6 +1775,7 @@ describe('GenericRowRenderer', () => {
dnsRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: requestEvent,
+ isDraggable: true,
timelineId: 'test',
})}
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 c6845d7d672d2..b1027bf12b7d2 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
@@ -40,12 +40,13 @@ export const createGenericSystemRowRenderer = ({
action.toLowerCase() === actionName
);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
@@ -71,12 +72,13 @@ export const createEndgameProcessRowRenderer = ({
action?.toLowerCase() === actionName
);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
(
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
(
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
(
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
(
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
@@ -239,12 +245,13 @@ export const createSocketRowRenderer = ({
const action: string | null | undefined = get('event.action[0]', ecs);
return action != null && action.toLowerCase() === actionName;
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
@@ -268,12 +275,13 @@ export const createSecurityEventRowRenderer = ({
action?.toLowerCase() === actionName
);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
@@ -287,12 +295,13 @@ export const createDnsRowRenderer = (): RowRenderer => ({
const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs);
return !isNillEmptyOrNotFinite(dnsQuestionType) && !isNillEmptyOrNotFinite(dnsQuestionName);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
@@ -315,12 +324,13 @@ export const createEndpointRegistryRowRenderer = ({
dataset?.toLowerCase() === 'endpoint.events.registry' && action?.toLowerCase() === actionName
);
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.tsx
index 7952154da1293..296c099da22a4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.tsx
@@ -13,13 +13,14 @@ import { TokensFlexItem } from '../helpers';
interface Props {
contextId: string;
eventId: string;
+ isDraggable?: boolean;
packageName: string | null | undefined;
packageSummary: string | null | undefined;
packageVersion: string | null | undefined;
}
export const Package = React.memo(
- ({ contextId, eventId, packageName, packageSummary, packageVersion }) => {
+ ({ contextId, eventId, isDraggable, packageName, packageSummary, packageVersion }) => {
if (packageName != null || packageSummary != null || packageVersion != null) {
return (
<>
@@ -28,6 +29,7 @@ export const Package = React.memo(
contextId={contextId}
eventId={eventId}
field="system.audit.package.name"
+ isDraggable={isDraggable}
value={packageName}
iconType="document"
/>
@@ -37,6 +39,7 @@ export const Package = React.memo(
contextId={contextId}
eventId={eventId}
field="system.audit.package.version"
+ isDraggable={isDraggable}
value={packageVersion}
iconType="document"
/>
@@ -46,6 +49,7 @@ export const Package = React.memo(
contextId={contextId}
eventId={eventId}
field="system.audit.package.summary"
+ isDraggable={isDraggable}
value={packageSummary}
/>
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 7cff1166cd0de..b61e00f1752b8 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
@@ -242,6 +242,7 @@ describe('UserHostWorkingDir', () => {
{
);
- expect(wrapper.find('[data-test-subj="draggable-content-user.domain"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="render-content-user.domain"]').exists()).toBe(true);
});
test('it renders a draggable with an overridden field name when userDomain is provided, and userDomainField is also specified as a prop', () => {
@@ -261,6 +262,7 @@ describe('UserHostWorkingDir', () => {
{
);
- expect(
- wrapper.find('[data-test-subj="draggable-content-overridden.field.name"]').exists()
- ).toBe(true);
+ expect(wrapper.find('[data-test-subj="render-content-overridden.field.name"]').exists()).toBe(
+ true
+ );
});
test('it renders a draggable `user.name` field (by default) when userName is provided, and userNameField is NOT specified as a prop', () => {
@@ -283,6 +285,7 @@ describe('UserHostWorkingDir', () => {
{
);
- expect(wrapper.find('[data-test-subj="draggable-content-user.name"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="render-content-user.name"]').exists()).toBe(true);
});
test('it renders a draggable with an overridden field name when userName is provided, and userNameField is also specified as a prop', () => {
@@ -302,6 +305,7 @@ describe('UserHostWorkingDir', () => {
{
);
- expect(
- wrapper.find('[data-test-subj="draggable-content-overridden.field.name"]').exists()
- ).toBe(true);
+ expect(wrapper.find('[data-test-subj="render-content-overridden.field.name"]').exists()).toBe(
+ true
+ );
});
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.tsx
index 0ab3624970c28..9e789cbd7aba2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.tsx
@@ -14,12 +14,13 @@ import { HostWorkingDir } from './host_working_dir';
interface Props {
contextId: string;
eventId: string;
+ isDraggable?: boolean;
+ hostName: string | null | undefined;
+ hostNameSeparator?: string;
userDomain: string | null | undefined;
userDomainField?: string;
userName: string | null | undefined;
userNameField?: string;
- hostName: string | null | undefined;
- hostNameSeparator?: string;
workingDirectory: string | null | undefined;
}
@@ -29,6 +30,7 @@ export const UserHostWorkingDir = React.memo(
eventId,
hostName,
hostNameSeparator = '@',
+ isDraggable,
userDomain,
userDomainField = 'user.domain',
userName,
@@ -42,6 +44,7 @@ export const UserHostWorkingDir = React.memo(
contextId={contextId}
eventId={eventId}
field={userNameField}
+ isDraggable={isDraggable}
value={userName}
iconType="user"
/>
@@ -61,6 +64,7 @@ export const UserHostWorkingDir = React.memo(
contextId={contextId}
eventId={eventId}
field={userDomainField}
+ isDraggable={isDraggable}
value={userDomain}
/>
@@ -76,6 +80,7 @@ export const UserHostWorkingDir = React.memo(
contextId={contextId}
eventId={eventId}
hostName={hostName}
+ isDraggable={isDraggable}
workingDirectory={workingDirectory}
/>
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
index 6c59df606cd36..94cbe43e93d2d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
@@ -525,6 +525,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = `
},
}
}
+ isDraggable={true}
timelineId="test"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.tsx
index 7b44862040f2d..a4dbde1a5626d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.tsx
@@ -24,15 +24,16 @@ Details.displayName = 'Details';
interface ZeekDetailsProps {
browserFields: BrowserFields;
data: Ecs;
+ isDraggable?: boolean;
timelineId: string;
}
-export const ZeekDetails = React.memo(({ data, timelineId }) =>
+export const ZeekDetails = React.memo(({ data, isDraggable, timelineId }) =>
data.zeek != null ? (
-
+
-
+
) : null
);
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 6b154d4d32707..12f2fd08163ba 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
@@ -44,6 +44,7 @@ describe('zeek_row_renderer', () => {
const children = zeekRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: nonZeek,
+ isDraggable: true,
timelineId: 'test',
});
@@ -63,6 +64,7 @@ describe('zeek_row_renderer', () => {
const children = zeekRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: zeek,
+ isDraggable: true,
timelineId: 'test',
});
const wrapper = mount(
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 2b6311b8cae83..0a265fa7522b1 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
@@ -21,9 +21,14 @@ export const zeekRowRenderer: RowRenderer = {
const module: string | null | undefined = get('event.module[0]', ecs);
return module != null && module.toLowerCase() === 'zeek';
},
- renderRow: ({ browserFields, data, timelineId }) => (
+ renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
-
+
),
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
index e4d5a6a86682d..412fd9d04fe7c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
@@ -67,9 +67,10 @@ export const sha1StringRenderer: StringRenderer = (value: string) =>
export const DraggableZeekElement = React.memo<{
id: string;
field: string;
+ isDraggable?: boolean;
value: string | null | undefined;
stringRenderer?: StringRenderer;
-}>(({ id, field, value, stringRenderer = defaultStringRenderer }) => {
+}>(({ id, field, isDraggable, value, stringRenderer = defaultStringRenderer }) => {
const dataProviderProp = useMemo(
() => ({
and: [],
@@ -105,7 +106,7 @@ export const DraggableZeekElement = React.memo<{
return value != null ? (
-
+
) : null;
});
@@ -203,10 +204,11 @@ export const constructDroppedValue = (dropped: boolean | null | undefined): stri
interface ZeekSignatureProps {
data: Ecs;
+ isDraggable?: boolean;
timelineId: string;
}
-export const ZeekSignature = React.memo(({ data, timelineId }) => {
+export const ZeekSignature = React.memo(({ data, isDraggable, timelineId }) => {
const id = `zeek-signature-draggable-zeek-element-${timelineId}-${data._id}`;
const sessionId: string | null | undefined = get('zeek.session_id[0]', data);
const dataSet: string | null | undefined = get('event.dataset[0]', data);
@@ -234,42 +236,92 @@ export const ZeekSignature = React.memo(({ data, timelineId
return (
<>
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
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 a68617536c6af..ef47b474350c7 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,7 +5,6 @@
* 2.0.
*/
-import { DataProvider } from '../../components/timeline/data_providers/data_provider';
import { EqlOptionsSelected } from '../../../../common/search_strategy/timeline';
import type {
TimelineEventsType,
@@ -26,8 +25,6 @@ export type TimelineModel = TGridModelForTimeline & {
prevActiveTab: TimelineTabs;
/** Timeline saved object owner */
createdBy?: string;
- /** The sources of the event data shown in the timeline */
- dataProviders: DataProvider[];
/** A summary of the events and notes in this timeline */
description: string;
eqlOptions: EqlOptionsSelected;
diff --git a/x-pack/plugins/timelines/common/types/timeline/rows/index.ts b/x-pack/plugins/timelines/common/types/timeline/rows/index.ts
index b598d13273798..089f886e1c221 100644
--- a/x-pack/plugins/timelines/common/types/timeline/rows/index.ts
+++ b/x-pack/plugins/timelines/common/types/timeline/rows/index.ts
@@ -15,10 +15,12 @@ export interface RowRenderer {
renderRow: ({
browserFields,
data,
+ isDraggable,
timelineId,
}: {
browserFields: BrowserFields;
data: Ecs;
+ isDraggable: boolean;
timelineId: string;
}) => React.ReactNode;
}
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
index eb9c95f0998c6..dd5ef27c32a89 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
@@ -5,18 +5,20 @@
* 2.0.
*/
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import { DraggableId } from 'react-beautiful-dnd';
+import { useDispatch } from 'react-redux';
+
+import { isEmpty } from 'lodash';
+import { DataProvider, stopPropagationAndPreventDefault, TimelineId } from '../../../../common';
import { TooltipWithKeyboardShortcut } from '../../tooltip_with_keyboard_shortcut';
import { getAdditionalScreenReaderOnlyContext } from '../utils';
import { useAddToTimeline } from '../../../hooks/use_add_to_timeline';
import { HoverActionComponentProps } from './types';
-
-const ADD_TO_TIMELINE = i18n.translate('xpack.timelines.hoverActions.addToTimeline', {
- defaultMessage: 'Add to timeline investigation',
-});
+import { tGridActions } from '../../..';
+import { useAppToasts } from '../../../hooks/use_app_toasts';
+import * as i18n from './translations';
export const ADD_TO_TIMELINE_KEYBOARD_SHORTCUT = 'a';
@@ -25,7 +27,7 @@ export interface UseGetHandleStartDragToTimelineArgs {
draggableId: DraggableId | undefined;
}
-export const useGetHandleStartDragToTimeline = ({
+const useGetHandleStartDragToTimeline = ({
field,
draggableId,
}: UseGetHandleStartDragToTimelineArgs): (() => void) => {
@@ -41,8 +43,59 @@ export const useGetHandleStartDragToTimeline = ({
return handleStartDragToTimeline;
};
-export const AddToTimelineButton: React.FC = React.memo(
- ({ field, onClick, ownFocus, showTooltip = false, value }) => {
+export interface AddToTimelineButtonProps extends HoverActionComponentProps {
+ draggableId?: DraggableId;
+ dataProvider?: DataProvider[] | DataProvider;
+}
+
+const AddToTimelineButton: React.FC = React.memo(
+ ({
+ closePopOver,
+ dataProvider,
+ defaultFocusedButtonRef,
+ draggableId,
+ field,
+ keyboardEvent,
+ ownFocus,
+ showTooltip = false,
+ value,
+ }) => {
+ const dispatch = useDispatch();
+ const { addSuccess } = useAppToasts();
+ const startDragToTimeline = useGetHandleStartDragToTimeline({ draggableId, field });
+ const handleStartDragToTimeline = useCallback(() => {
+ if (draggableId != null) {
+ startDragToTimeline();
+ } else if (!isEmpty(dataProvider)) {
+ const addDataProvider = Array.isArray(dataProvider) ? dataProvider : [dataProvider];
+ addDataProvider.forEach((provider) => {
+ if (provider) {
+ dispatch(
+ tGridActions.addProviderToTimeline({
+ id: TimelineId.active,
+ dataProvider: provider,
+ })
+ );
+ addSuccess(i18n.ADDED_TO_TIMELINE_MESSAGE(provider.name));
+ }
+ });
+ }
+
+ if (closePopOver != null) {
+ closePopOver();
+ }
+ }, [addSuccess, closePopOver, dataProvider, dispatch, draggableId, startDragToTimeline]);
+
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === ADD_TO_TIMELINE_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleStartDragToTimeline();
+ }
+ }, [handleStartDragToTimeline, keyboardEvent, ownFocus]);
+
return showTooltip ? (
= React.me
field,
value,
})}
- content={ADD_TO_TIMELINE}
+ content={i18n.ADD_TO_TIMELINE}
shortcut={ADD_TO_TIMELINE_KEYBOARD_SHORTCUT}
showShortcut={ownFocus}
/>
}
>
) : (
);
}
);
AddToTimelineButton.displayName = 'AddToTimelineButton';
+
+// eslint-disable-next-line import/no-default-export
+export { AddToTimelineButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
index 52d8fb439526f..d59383b8553ea 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
@@ -5,9 +5,11 @@
* 2.0.
*/
-import React from 'react';
+import React, { useCallback, useEffect } from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+
+import { stopPropagationAndPreventDefault } from '../../../../common';
import { TooltipWithKeyboardShortcut } from '../../tooltip_with_keyboard_shortcut';
import { getAdditionalScreenReaderOnlyContext } from '../utils';
import { defaultColumnHeaderType } from '../../t_grid/body/column_headers/default_headers';
@@ -30,28 +32,48 @@ export const NESTED_COLUMN = (field: string) =>
export const COLUMN_TOGGLE_KEYBOARD_SHORTCUT = 'i';
-export interface ColumnToggleFnArgs {
- toggleColumn: (column: ColumnHeaderOptions) => void;
- field: string;
-}
-
-export const columnToggleFn = ({ toggleColumn, field }: ColumnToggleFnArgs): void => {
- return toggleColumn({
- columnHeaderType: defaultColumnHeaderType,
- id: field,
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- });
-};
-
export interface ColumnToggleProps extends HoverActionComponentProps {
isDisabled: boolean;
isObjectArray: boolean;
+ toggleColumn: (column: ColumnHeaderOptions) => void;
}
-export const ColumnToggleButton: React.FC = React.memo(
- ({ field, isDisabled, isObjectArray, onClick, ownFocus, showTooltip = false, value }) => {
+const ColumnToggleButton: React.FC = React.memo(
+ ({
+ closePopOver,
+ defaultFocusedButtonRef,
+ field,
+ isDisabled,
+ isObjectArray,
+ keyboardEvent,
+ ownFocus,
+ showTooltip = false,
+ toggleColumn,
+ value,
+ }) => {
const label = isObjectArray ? NESTED_COLUMN(field) : COLUMN_TOGGLE(field);
+ const handleToggleColumn = useCallback(() => {
+ toggleColumn({
+ columnHeaderType: defaultColumnHeaderType,
+ id: field,
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ });
+ if (closePopOver != null) {
+ closePopOver();
+ }
+ }, [closePopOver, field, toggleColumn]);
+
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === COLUMN_TOGGLE_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleToggleColumn();
+ }
+ }, [handleToggleColumn, keyboardEvent, ownFocus]);
+
return showTooltip ? (
= React.memo(
>
= React.memo(
id={field}
iconSize="s"
iconType="listAdd"
- onClick={onClick}
+ onClick={handleToggleColumn}
/>
) : (
= React.memo(
id={field}
iconSize="s"
iconType="listAdd"
- onClick={onClick}
+ onClick={handleToggleColumn}
/>
);
}
);
ColumnToggleButton.displayName = 'ColumnToggleButton';
+
+// eslint-disable-next-line import/no-default-export
+export { ColumnToggleButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
index 33cc71e12c46f..1b567dee50683 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
@@ -5,10 +5,12 @@
* 2.0.
*/
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import { i18n } from '@kbn/i18n';
+import { stopPropagationAndPreventDefault } from '../../../../common';
import { WithCopyToClipboard } from '../../clipboard/with_copy_to_clipboard';
import { HoverActionComponentProps } from './types';
+import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../clipboard';
export const FIELD = i18n.translate('xpack.timelines.hoverActions.fieldLabel', {
defaultMessage: 'Field',
@@ -16,22 +18,45 @@ export const FIELD = i18n.translate('xpack.timelines.hoverActions.fieldLabel', {
export const COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT = 'c';
-export type CopyProps = Omit & {
+export interface CopyProps extends HoverActionComponentProps {
isHoverAction?: boolean;
-};
+}
-export const CopyButton: React.FC = React.memo(
- ({ field, isHoverAction, ownFocus, value }) => {
+const CopyButton: React.FC = React.memo(
+ ({ closePopOver, field, isHoverAction, keyboardEvent, ownFocus, value }) => {
+ const panelRef = useRef(null);
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ const copyToClipboardButton = panelRef.current?.querySelector(
+ `.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
+ );
+ if (copyToClipboardButton != null) {
+ copyToClipboardButton.click();
+ }
+ if (closePopOver != null) {
+ closePopOver();
+ }
+ }
+ }, [closePopOver, keyboardEvent, ownFocus]);
return (
-
+
+
+
);
}
);
CopyButton.displayName = 'CopyButton';
+
+// eslint-disable-next-line import/no-default-export
+export { CopyButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
index 421cd0089c1b7..58f7b4a831e51 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
@@ -5,9 +5,11 @@
* 2.0.
*/
-import React from 'react';
+import React, { useCallback, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonIcon, EuiButtonIconPropsForButton, EuiToolTip } from '@elastic/eui';
+import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+
+import { stopPropagationAndPreventDefault } from '../../../../common';
import { TooltipWithKeyboardShortcut } from '../../tooltip_with_keyboard_shortcut';
import { createFilter, getAdditionalScreenReaderOnlyContext } from '../utils';
import { HoverActionComponentProps, FilterValueFnArgs } from './types';
@@ -17,34 +19,50 @@ export const FILTER_FOR_VALUE = i18n.translate('xpack.timelines.hoverActions.fil
});
export const FILTER_FOR_VALUE_KEYBOARD_SHORTCUT = 'f';
-export const filterForValueFn = ({
- field,
- value,
- filterManager,
- onFilterAdded,
-}: FilterValueFnArgs): void => {
- const makeFilter = (currentVal: string | null | undefined) =>
- currentVal?.length === 0 ? createFilter(field, undefined) : createFilter(field, currentVal);
- const filters = Array.isArray(value)
- ? value.map((currentVal: string | null | undefined) => makeFilter(currentVal))
- : makeFilter(value);
+export type FilterForValueProps = HoverActionComponentProps & FilterValueFnArgs;
- const activeFilterManager = filterManager;
+const FilterForValueButton: React.FC = React.memo(
+ ({
+ closePopOver,
+ defaultFocusedButtonRef,
+ field,
+ filterManager,
+ keyboardEvent,
+ onFilterAdded,
+ ownFocus,
+ showTooltip = false,
+ value,
+ }) => {
+ const filterForValueFn = useCallback(() => {
+ const makeFilter = (currentVal: string | null | undefined) =>
+ currentVal?.length === 0 ? createFilter(field, undefined) : createFilter(field, currentVal);
+ const filters = Array.isArray(value)
+ ? value.map((currentVal: string | null | undefined) => makeFilter(currentVal))
+ : makeFilter(value);
- if (activeFilterManager != null) {
- activeFilterManager.addFilters(filters);
- if (onFilterAdded != null) {
- onFilterAdded();
- }
- }
-};
+ const activeFilterManager = filterManager;
-export interface FilterForValueProps extends HoverActionComponentProps {
- defaultFocusedButtonRef: EuiButtonIconPropsForButton['buttonRef'];
-}
+ if (activeFilterManager != null) {
+ activeFilterManager.addFilters(filters);
+ if (onFilterAdded != null) {
+ onFilterAdded();
+ }
+ }
+ if (closePopOver != null) {
+ closePopOver();
+ }
+ }, [closePopOver, field, filterManager, onFilterAdded, value]);
+
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === FILTER_FOR_VALUE_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ filterForValueFn();
+ }
+ }, [filterForValueFn, keyboardEvent, ownFocus]);
-export const FilterForValueButton: React.FC = React.memo(
- ({ defaultFocusedButtonRef, field, onClick, ownFocus, showTooltip = false, value }) => {
return showTooltip ? (
= React.memo(
data-test-subj="filter-for-value"
iconSize="s"
iconType="plusInCircle"
- onClick={onClick}
+ onClick={filterForValueFn}
/>
) : (
@@ -77,10 +95,13 @@ export const FilterForValueButton: React.FC = React.memo(
data-test-subj="filter-for-value"
iconSize="s"
iconType="plusInCircle"
- onClick={onClick}
+ onClick={filterForValueFn}
/>
);
}
);
FilterForValueButton.displayName = 'FilterForValueButton';
+
+// eslint-disable-next-line import/no-default-export
+export { FilterForValueButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
index bfa7848025bf4..03150d6371397 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
@@ -5,9 +5,11 @@
* 2.0.
*/
-import React from 'react';
+import React, { useCallback, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+
+import { stopPropagationAndPreventDefault } from '../../../../common';
import { TooltipWithKeyboardShortcut } from '../../tooltip_with_keyboard_shortcut';
import { createFilter, getAdditionalScreenReaderOnlyContext } from '../utils';
import { HoverActionComponentProps, FilterValueFnArgs } from './types';
@@ -18,32 +20,50 @@ export const FILTER_OUT_VALUE = i18n.translate('xpack.timelines.hoverActions.fil
export const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'o';
-export const filterOutValueFn = ({
- field,
- value,
- filterManager,
- onFilterAdded,
-}: FilterValueFnArgs) => {
- const makeFilter = (currentVal: string | null | undefined) =>
- currentVal?.length === 0
- ? createFilter(field, null, false)
- : createFilter(field, currentVal, true);
- const filters = Array.isArray(value)
- ? value.map((currentVal: string | null | undefined) => makeFilter(currentVal))
- : makeFilter(value);
+const FilterOutValueButton: React.FC = React.memo(
+ ({
+ closePopOver,
+ defaultFocusedButtonRef,
+ field,
+ filterManager,
+ keyboardEvent,
+ onFilterAdded,
+ ownFocus,
+ showTooltip = false,
+ value,
+ }) => {
+ const filterOutValueFn = useCallback(() => {
+ const makeFilter = (currentVal: string | null | undefined) =>
+ currentVal?.length === 0
+ ? createFilter(field, null, false)
+ : createFilter(field, currentVal, true);
+ const filters = Array.isArray(value)
+ ? value.map((currentVal: string | null | undefined) => makeFilter(currentVal))
+ : makeFilter(value);
- const activeFilterManager = filterManager;
+ const activeFilterManager = filterManager;
- if (activeFilterManager != null) {
- activeFilterManager.addFilters(filters);
- if (onFilterAdded != null) {
- onFilterAdded();
- }
- }
-};
+ if (activeFilterManager != null) {
+ activeFilterManager.addFilters(filters);
+ if (onFilterAdded != null) {
+ onFilterAdded();
+ }
+ }
+ if (closePopOver != null) {
+ closePopOver();
+ }
+ }, [closePopOver, field, filterManager, onFilterAdded, value]);
+
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === FILTER_OUT_VALUE_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ filterOutValueFn();
+ }
+ }, [filterOutValueFn, keyboardEvent, ownFocus]);
-export const FilterOutValueButton: React.FC = React.memo(
- ({ field, onClick, ownFocus, showTooltip = false, value }) => {
return showTooltip ? (
= React.m
>
) : (
);
}
);
FilterOutValueButton.displayName = 'FilterOutValueButton';
+
+// eslint-disable-next-line import/no-default-export
+export { FilterOutValueButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/translations.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/translations.tsx
new file mode 100644
index 0000000000000..2f8587ddfab49
--- /dev/null
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/translations.tsx
@@ -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 { i18n } from '@kbn/i18n';
+
+export const ADD_TO_TIMELINE = i18n.translate('xpack.timelines.hoverActions.addToTimeline', {
+ defaultMessage: 'Add to timeline investigation',
+});
+
+export const ADDED_TO_TIMELINE_MESSAGE = (fieldOrValue: string) =>
+ i18n.translate('xpack.timelines.hoverActions.addToTimeline.addedFieldMessage', {
+ values: { fieldOrValue },
+ defaultMessage: `Added {fieldOrValue} to timeline`,
+ });
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts b/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
index 4999638e0fe81..fdef1403e3dc2 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
@@ -4,6 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
+import { EuiButtonIconPropsForButton } from '@elastic/eui';
import { FilterManager } from '../../../../../../../src/plugins/data/public';
export interface FilterValueFnArgs {
@@ -14,8 +16,10 @@ export interface FilterValueFnArgs {
}
export interface HoverActionComponentProps {
+ closePopOver?: () => void;
+ defaultFocusedButtonRef?: EuiButtonIconPropsForButton['buttonRef'];
field: string;
- onClick?: () => void;
+ keyboardEvent?: React.KeyboardEvent;
ownFocus: boolean;
showTooltip?: boolean;
value?: string[] | string | null;
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/index.tsx b/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
index 2329134d85626..fc8fcfa488a76 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
@@ -4,86 +4,83 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
-import {
- AddToTimelineButton,
- ADD_TO_TIMELINE_KEYBOARD_SHORTCUT,
- UseGetHandleStartDragToTimelineArgs,
- useGetHandleStartDragToTimeline,
-} from './actions/add_to_timeline';
-import {
- ColumnToggleButton,
- columnToggleFn,
- ColumnToggleFnArgs,
- ColumnToggleProps,
- COLUMN_TOGGLE_KEYBOARD_SHORTCUT,
-} from './actions/column_toggle';
-import { CopyButton, CopyProps, COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT } from './actions/copy';
-import {
- FilterForValueButton,
- filterForValueFn,
- FilterForValueProps,
- FILTER_FOR_VALUE_KEYBOARD_SHORTCUT,
-} from './actions/filter_for_value';
-import {
- FilterOutValueButton,
- filterOutValueFn,
- FILTER_OUT_VALUE_KEYBOARD_SHORTCUT,
-} from './actions/filter_out_value';
-import { HoverActionComponentProps, FilterValueFnArgs } from './actions/types';
+import { EuiLoadingSpinner } from '@elastic/eui';
+import { I18nProvider } from '@kbn/i18n/react';
+import React, { ReactElement } from 'react';
+import { Provider } from 'react-redux';
+import { Store } from 'redux';
+import type { AddToTimelineButtonProps } from './actions/add_to_timeline';
+import type { ColumnToggleProps } from './actions/column_toggle';
+import type { CopyProps } from './actions/copy';
+import type { HoverActionComponentProps, FilterValueFnArgs } from './actions/types';
export interface HoverActionsConfig {
- addToTimeline: {
- AddToTimelineButton: React.FC;
- keyboardShortcut: string;
- useGetHandleStartDragToTimeline: (args: UseGetHandleStartDragToTimelineArgs) => () => void;
- };
- columnToggle: {
- ColumnToggleButton: React.FC;
- columnToggleFn: (args: ColumnToggleFnArgs) => void;
- keyboardShortcut: string;
- };
- copy: {
- CopyButton: React.FC;
- keyboardShortcut: string;
- };
- filterForValue: {
- FilterForValueButton: React.FC;
- filterForValueFn: (args: FilterValueFnArgs) => void;
- keyboardShortcut: string;
- };
- filterOutValue: {
- FilterOutValueButton: React.FC;
- filterOutValueFn: (args: FilterValueFnArgs) => void;
- keyboardShortcut: string;
- };
+ getAddToTimelineButton: (
+ props: AddToTimelineButtonProps
+ ) => ReactElement;
+ getColumnToggleButton: (props: ColumnToggleProps) => ReactElement;
+ getCopyButton: (props: CopyProps) => ReactElement;
+ getFilterForValueButton: (
+ props: HoverActionComponentProps & FilterValueFnArgs
+ ) => ReactElement;
+ getFilterOutValueButton: (
+ props: HoverActionComponentProps & FilterValueFnArgs
+ ) => ReactElement;
}
-export const addToTimeline = {
- AddToTimelineButton,
- keyboardShortcut: ADD_TO_TIMELINE_KEYBOARD_SHORTCUT,
- useGetHandleStartDragToTimeline,
+const AddToTimelineButtonLazy = React.lazy(() => import('./actions/add_to_timeline'));
+const getAddToTimelineButtonLazy = (store: Store, props: AddToTimelineButtonProps) => {
+ return (
+ }>
+
+
+
+
+
+
+ );
};
-export const columnToggle = {
- ColumnToggleButton,
- columnToggleFn,
- keyboardShortcut: COLUMN_TOGGLE_KEYBOARD_SHORTCUT,
+const ColumnToggleButtonLazy = React.lazy(() => import('./actions/column_toggle'));
+const getColumnToggleButtonLazy = (props: ColumnToggleProps) => {
+ return (
+ }>
+
+
+ );
};
-export const copy = {
- CopyButton,
- keyboardShortcut: COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT,
+const CopyButtonLazy = React.lazy(() => import('./actions/copy'));
+const getCopyButtonLazy = (props: CopyProps) => {
+ return (
+ }>
+
+
+ );
};
-export const filterForValue = {
- FilterForValueButton,
- filterForValueFn,
- keyboardShortcut: FILTER_FOR_VALUE_KEYBOARD_SHORTCUT,
+const FilterForValueButtonLazy = React.lazy(() => import('./actions/filter_for_value'));
+const getFilterForValueButtonLazy = (props: HoverActionComponentProps & FilterValueFnArgs) => {
+ return (
+ }>
+
+
+ );
};
-export const filterOutValue = {
- FilterOutValueButton,
- filterOutValueFn,
- keyboardShortcut: FILTER_OUT_VALUE_KEYBOARD_SHORTCUT,
+const FilterOutValueButtonLazy = React.lazy(() => import('./actions/filter_out_value'));
+const getFilterOutValueButtonLazy = (props: HoverActionComponentProps & FilterValueFnArgs) => {
+ return (
+ }>
+
+
+ );
};
+
+export const getHoverActions = (store?: Store): HoverActionsConfig => ({
+ getAddToTimelineButton: getAddToTimelineButtonLazy.bind(null, store!),
+ getColumnToggleButton: getColumnToggleButtonLazy,
+ getCopyButton: getCopyButtonLazy,
+ getFilterForValueButton: getFilterForValueButtonLazy,
+ getFilterOutValueButton: getFilterOutValueButtonLazy,
+});
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx
index 65762b93cd43f..0d606ad28eff2 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx
@@ -80,6 +80,7 @@ export const StatefulRowRenderer = ({
{rowRenderer.renderRow({
browserFields,
data: event.ecs,
+ isDraggable: false,
timelineId,
})}
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.test.tsx
index 5cd709d2de3c7..1a4bfcb0e4ab5 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.test.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/renderers/plain_row_renderer.test.tsx
@@ -23,6 +23,7 @@ describe('plain_row_renderer', () => {
const children = plainRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockDatum,
+ isDraggable: false,
timelineId: 'test',
});
const wrapper = shallow({children} );
@@ -37,6 +38,7 @@ describe('plain_row_renderer', () => {
const children = plainRowRenderer.renderRow({
browserFields: mockBrowserFields,
data: mockDatum,
+ isDraggable: false,
timelineId: 'test',
});
const wrapper = mount({children} );
diff --git a/x-pack/plugins/timelines/public/mock/global_state.ts b/x-pack/plugins/timelines/public/mock/global_state.ts
index f7d3297738373..610d1b26f2351 100644
--- a/x-pack/plugins/timelines/public/mock/global_state.ts
+++ b/x-pack/plugins/timelines/public/mock/global_state.ts
@@ -17,6 +17,7 @@ export const mockGlobalState: TimelineState = {
start: '2020-07-07T08:20:18.966Z',
end: '2020-07-08T08:20:18.966Z',
},
+ dataProviders: [],
deletedEventIds: [],
excludedRowRendererIds: [],
expandedDetail: {},
diff --git a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
index 52ea1fa827136..5a8afb2036abf 100644
--- a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
+++ b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
@@ -8,28 +8,9 @@ import React from 'react';
/* eslint-disable react/display-name */
export const mockHoverActions = {
- addToTimeline: {
- AddToTimelineButton: () => <>{'Add To Timeline'}>,
- keyboardShortcut: 'timelineAddShortcut',
- useGetHandleStartDragToTimeline: () => jest.fn,
- },
- columnToggle: {
- ColumnToggleButton: () => <>{'Column Toggle'}>,
- columnToggleFn: jest.fn,
- keyboardShortcut: 'columnToggleShortcut',
- },
- copy: {
- CopyButton: () => <>{'Copy button'}>,
- keyboardShortcut: 'copyShortcut',
- },
- filterForValue: {
- FilterForValueButton: () => <>{'Filter button'}>,
- filterForValueFn: jest.fn,
- keyboardShortcut: 'filterForShortcut',
- },
- filterOutValue: {
- FilterOutValueButton: () => <>{'Filter out button'}>,
- filterOutValueFn: jest.fn,
- keyboardShortcut: 'filterOutShortcut',
- },
+ getAddToTimelineButton: () => <>{'Add To Timeline'}>,
+ getColumnToggleButton: () => <>{'Column Toggle'}>,
+ getCopyButton: () => <>{'Copy button'}>,
+ getFilterForValueButton: () => <>{'Filter button'}>,
+ getFilterOutValueButton: () => <>{'Filter out button'}>,
};
diff --git a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts
index 31b6ea9e665ac..56631c498c755 100644
--- a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts
+++ b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts
@@ -1549,6 +1549,7 @@ export const mockTgridModel: TGridModel = {
initialWidth: 180,
},
],
+ dataProviders: [],
defaultColumns: [],
queryFields: [],
dateRange: {
diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts
index 29d84331cffaa..cb931ff53d445 100644
--- a/x-pack/plugins/timelines/public/plugin.ts
+++ b/x-pack/plugins/timelines/public/plugin.ts
@@ -26,7 +26,7 @@ import type { TimelinesUIStart, TGridProps, TimelinesStartPlugins } from './type
import { tGridReducer } from './store/t_grid/reducer';
import { useDraggableKeyboardWrapper } from './components/drag_and_drop/draggable_keyboard_wrapper_hook';
import { useAddToTimeline, useAddToTimelineSensor } from './hooks/use_add_to_timeline';
-import * as hoverActions from './components/hover_actions';
+import { getHoverActions } from './components/hover_actions';
export class TimelinesPlugin implements Plugin {
constructor(private readonly initializerContext: PluginInitializerContext) {}
private _store: Store | undefined;
@@ -41,7 +41,7 @@ export class TimelinesPlugin implements Plugin {
}
return {
getHoverActions: () => {
- return hoverActions;
+ return getHoverActions(this._store!);
},
getTGrid: (props: TGridProps) => {
return getTGridLazy(props, {
diff --git a/x-pack/plugins/timelines/public/store/t_grid/actions.ts b/x-pack/plugins/timelines/public/store/t_grid/actions.ts
index 6d9e9e5bc7379..64c4d8a78c7ac 100644
--- a/x-pack/plugins/timelines/public/store/t_grid/actions.ts
+++ b/x-pack/plugins/timelines/public/store/t_grid/actions.ts
@@ -9,6 +9,7 @@ import actionCreatorFactory from 'typescript-fsa';
import type { TimelineNonEcsData } from '../../../common/search_strategy';
import type {
ColumnHeaderOptions,
+ DataProvider,
SortColumnTimeline,
TimelineExpandedDetailType,
} from '../../../common/types/timeline';
@@ -100,3 +101,7 @@ export const initializeTGridSettings = actionCreator('I
export const setTGridSelectAll = actionCreator<{ id: string; selectAll: boolean }>(
'SET_TGRID_SELECT_ALL'
);
+
+export const addProviderToTimeline = actionCreator<{ id: string; dataProvider: DataProvider }>(
+ 'ADD_PROVIDER_TO_TIMELINE'
+);
diff --git a/x-pack/plugins/timelines/public/store/t_grid/helpers.ts b/x-pack/plugins/timelines/public/store/t_grid/helpers.ts
index 8bcf246dadb03..dd056f1e9237a 100644
--- a/x-pack/plugins/timelines/public/store/t_grid/helpers.ts
+++ b/x-pack/plugins/timelines/public/store/t_grid/helpers.ts
@@ -12,7 +12,11 @@ import type { ToggleDetailPanel } from './actions';
import { TGridPersistInput, TimelineById, TimelineId } from './types';
import type { TGridModel, TGridModelSettings } from './model';
-import type { ColumnHeaderOptions, SortColumnTimeline } from '../../../common/types/timeline';
+import type {
+ ColumnHeaderOptions,
+ DataProvider,
+ SortColumnTimeline,
+} from '../../../common/types/timeline';
import { getTGridManageDefaults, tGridDefaults } from './defaults';
export const isNotNull = (value: T | null): value is T => value !== null;
@@ -421,3 +425,35 @@ export const updateTimelineDetailsPanel = (action: ToggleDetailPanel) => {
[expandedTabType]: {},
};
};
+
+export const addProviderToTimelineHelper = (
+ id: string,
+ provider: DataProvider,
+ timelineById: TimelineById
+): TimelineById => {
+ const timeline = timelineById[id];
+ const alreadyExistsAtIndex = timeline.dataProviders.findIndex((p) => p.id === provider.id);
+
+ if (alreadyExistsAtIndex > -1 && !isEmpty(timeline.dataProviders[alreadyExistsAtIndex].and)) {
+ provider.id = `${provider.id}-${
+ timeline.dataProviders.filter((p) => p.id === provider.id).length
+ }`;
+ }
+
+ const dataProviders =
+ alreadyExistsAtIndex > -1 && isEmpty(timeline.dataProviders[alreadyExistsAtIndex].and)
+ ? [
+ ...timeline.dataProviders.slice(0, alreadyExistsAtIndex),
+ provider,
+ ...timeline.dataProviders.slice(alreadyExistsAtIndex + 1),
+ ]
+ : [...timeline.dataProviders, provider];
+
+ return {
+ ...timelineById,
+ [id]: {
+ ...timeline,
+ dataProviders,
+ },
+ };
+};
diff --git a/x-pack/plugins/timelines/public/store/t_grid/model.ts b/x-pack/plugins/timelines/public/store/t_grid/model.ts
index 2d4f9f0fca35f..4ed4448f4cf35 100644
--- a/x-pack/plugins/timelines/public/store/t_grid/model.ts
+++ b/x-pack/plugins/timelines/public/store/t_grid/model.ts
@@ -10,6 +10,7 @@ import type { Filter, FilterManager } from '../../../../../../src/plugins/data/p
import type { TimelineNonEcsData } from '../../../common/search_strategy';
import type {
ColumnHeaderOptions,
+ DataProvider,
TimelineExpandedDetail,
SortColumnTimeline,
SerializedFilterQuery,
@@ -39,6 +40,8 @@ export interface TGridModel extends TGridModelSettings {
Pick &
ColumnHeaderOptions
>;
+ /** The sources of the event data shown in the timeline */
+ dataProviders: DataProvider[];
/** Specifies the granularity of the date range (e.g. 1 Day / Week / Month) applicable to the mini-map */
dateRange: {
start: string;
@@ -81,6 +84,7 @@ export interface TGridModel extends TGridModelSettings {
export type TGridModelForTimeline = Pick<
TGridModel,
| 'columns'
+ | 'dataProviders'
| 'dateRange'
| 'deletedEventIds'
| 'excludedRowRendererIds'
diff --git a/x-pack/plugins/timelines/public/store/t_grid/reducer.ts b/x-pack/plugins/timelines/public/store/t_grid/reducer.ts
index 57c45f857554d..751837691ea10 100644
--- a/x-pack/plugins/timelines/public/store/t_grid/reducer.ts
+++ b/x-pack/plugins/timelines/public/store/t_grid/reducer.ts
@@ -7,6 +7,7 @@
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import {
+ addProviderToTimeline,
applyDeltaToColumnWidth,
clearEventsDeleted,
clearEventsLoading,
@@ -28,6 +29,7 @@ import {
} from './actions';
import {
+ addProviderToTimelineHelper,
applyDeltaToTimelineColumnWidth,
createInitTGrid,
setInitializeTgridSettings,
@@ -209,4 +211,8 @@ export const tGridReducer = reducerWithInitialState(initialTGridState)
},
},
}))
+ .case(addProviderToTimeline, (state, { id, dataProvider }) => ({
+ ...state,
+ timelineById: addProviderToTimelineHelper(id, dataProvider, state.timelineById),
+ }))
.build();
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
index 07f4072e02b85..dff2ba17cb3f0 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
@@ -247,6 +247,7 @@ export const ExpandedRow: FC = ({ item }) => {
onTabClick={() => {}}
expand={false}
style={{ width: '100%' }}
+ data-test-subj="transformExpandedRowTabbedContent"
/>
);
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_search_bar_filters.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_search_bar_filters.tsx
index 7794d65934d6d..d9ee384f3ec69 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_search_bar_filters.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_search_bar_filters.tsx
@@ -9,7 +9,12 @@ import React from 'react';
import { EuiBadge, SearchFilterConfig } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TermClause, FieldClause, Value } from './common';
-import { TRANSFORM_MODE, TRANSFORM_STATE } from '../../../../../../common/constants';
+import {
+ TRANSFORM_FUNCTION,
+ TRANSFORM_MODE,
+ TRANSFORM_STATE,
+} from '../../../../../../common/constants';
+import { isLatestTransform, isPivotTransform } from '../../../../../../common/types/transform';
import { TransformListRow } from '../../../../common';
import { getTaskStateBadge } from './use_columns';
@@ -93,7 +98,20 @@ export const filterTransforms = (
// the status value is an array of string(s) e.g. ['failed', 'stopped']
ts = transforms.filter((transform) => (c.value as Value[]).includes(transform.stats.state));
} else {
- ts = transforms.filter((transform) => transform.mode === c.value);
+ ts = transforms.filter((transform) => {
+ if (c.field === 'mode') {
+ return transform.mode === c.value;
+ }
+ if (c.field === 'type') {
+ if (c.value === TRANSFORM_FUNCTION.PIVOT) {
+ return isPivotTransform(transform.config);
+ }
+ if (c.value === TRANSFORM_FUNCTION.LATEST) {
+ return isLatestTransform(transform.config);
+ }
+ }
+ return false;
+ });
}
}
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx
index f3974430b662c..af2325ede2021 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx
@@ -20,13 +20,14 @@ describe('Transform: Job List Columns', () => {
const columns: ReturnType['columns'] = result.current.columns;
- expect(columns).toHaveLength(7);
+ expect(columns).toHaveLength(8);
expect(columns[0].isExpander).toBeTruthy();
expect(columns[1].name).toBe('ID');
expect(columns[2].name).toBe('Description');
- expect(columns[3].name).toBe('Status');
- expect(columns[4].name).toBe('Mode');
- expect(columns[5].name).toBe('Progress');
- expect(columns[6].name).toBe('Actions');
+ expect(columns[3].name).toBe('Type');
+ expect(columns[4].name).toBe('Status');
+ expect(columns[5].name).toBe('Mode');
+ expect(columns[6].name).toBe('Progress');
+ expect(columns[7].name).toBe('Actions');
});
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx
index e186acf31d34f..dbdd3409c7e34 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx
@@ -23,7 +23,11 @@ import {
RIGHT_ALIGNMENT,
} from '@elastic/eui';
-import { TransformId } from '../../../../../../common/types/transform';
+import {
+ isLatestTransform,
+ isPivotTransform,
+ TransformId,
+} from '../../../../../../common/types/transform';
import { TransformStats } from '../../../../../../common/types/transform_stats';
import { TRANSFORM_STATE } from '../../../../../../common/constants';
@@ -95,6 +99,7 @@ export const useColumns = (
EuiTableComputedColumnType,
EuiTableComputedColumnType,
EuiTableComputedColumnType,
+ EuiTableComputedColumnType,
EuiTableActionsColumnType
] = [
{
@@ -145,6 +150,27 @@ export const useColumns = (
sortable: true,
truncateText: true,
},
+ {
+ name: i18n.translate('xpack.transform.type', { defaultMessage: 'Type' }),
+ 'data-test-subj': 'transformListColumnType',
+ sortable: (item: TransformListRow) => item.mode,
+ truncateText: true,
+ render(item: TransformListRow) {
+ let transformType = i18n.translate('xpack.transform.type.unknown', {
+ defaultMessage: 'unknown',
+ });
+ if (isPivotTransform(item.config) === true) {
+ transformType = i18n.translate('xpack.transform.type.pivot', { defaultMessage: 'pivot' });
+ }
+ if (isLatestTransform(item.config) === true) {
+ transformType = i18n.translate('xpack.transform.type.latest', {
+ defaultMessage: 'latest',
+ });
+ }
+ return {transformType} ;
+ },
+ width: '100px',
+ },
{
name: i18n.translate('xpack.transform.status', { defaultMessage: 'Status' }),
'data-test-subj': 'transformListColumnStatus',
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 72b3e14bd55be..0e8a0d8e45a7d 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -19120,9 +19120,6 @@
"xpack.reporting.diagnostic.configSizeMismatch": "xpack.reporting.{KIBANA_MAX_SIZE_BYTES_PATH} ({kibanaMaxContentBytes}) はElasticSearchの{ES_MAX_SIZE_BYTES_PATH} ({elasticSearchMaxContentBytes}) を超えています。ElasticSearchで一致する{ES_MAX_SIZE_BYTES_PATH}を設定してください。あるいは、Kibanaでxpack.reporting.{KIBANA_MAX_SIZE_BYTES_PATH}を低くしてください。",
"xpack.reporting.diagnostic.noUsableSandbox": "Chromiumサンドボックスを使用できません。これは「xpack.reporting.capture.browser.chromium.disableSandbox」で無効にすることができます。この作業はご自身の責任で行ってください。{url}を参照してください",
"xpack.reporting.diagnostic.screenshotFailureMessage": "Kibanaインストールのスクリーンショットを作成できませんでした。",
- "xpack.reporting.errorButton.showReportErrorAriaLabel": "レポートエラーを表示",
- "xpack.reporting.errorButton.unableToFetchReportContentTitle": "レポートのコンテンツを取得できません",
- "xpack.reporting.errorButton.unableToGenerateReportTitle": "レポートを生成できません",
"xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました。{encryptionKey}が設定されていることを確認してこのレポートを再生成してください。{err}",
"xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "ジョブヘッダーがありません",
"xpack.reporting.exportTypes.csv.executeJob.dateFormateSetting": "Kibana の高度な設定「{dateFormatTimezone}」が「ブラウザー」に設定されています。あいまいさを避けるために日付は UTC 形式に変換されます。",
@@ -19133,7 +19130,6 @@
"xpack.reporting.exportTypes.printablePdf.logoDescription": "Elastic 提供",
"xpack.reporting.exportTypes.printablePdf.pagingDescription": "{pageCount} ページ中 {currentPage} ページ目",
"xpack.reporting.jobsQuery.deleteError": "レポートを削除できません:{error}",
- "xpack.reporting.jobStatuses.cancelledText": "キャンセル済み",
"xpack.reporting.jobStatuses.completedText": "完了",
"xpack.reporting.jobStatuses.failedText": "失敗",
"xpack.reporting.jobStatuses.pendingText": "保留中",
@@ -19159,7 +19155,6 @@
"xpack.reporting.listing.reports.subtitle": "Kibanaアプリケーションで生成されたレポートを取得します。",
"xpack.reporting.listing.reportstitle": "レポート",
"xpack.reporting.listing.table.captionDescription": "Kibanaアプリケーションでレポートが生成されました",
- "xpack.reporting.listing.table.csvContainsFormulas": "CSVには、スプレッドシートアプリケーションで式と解釈される可能性のある文字が含まれています。",
"xpack.reporting.listing.table.deleteCancelButton": "キャンセル",
"xpack.reporting.listing.table.deleteConfim": "{reportTitle} レポートを削除しました",
"xpack.reporting.listing.table.deleteConfirmButton": "削除",
@@ -19171,17 +19166,12 @@
"xpack.reporting.listing.table.downloadReport": "レポートをダウンロード",
"xpack.reporting.listing.table.downloadReportAriaLabel": "レポートをダウンロード",
"xpack.reporting.listing.table.loadingReportsDescription": "レポートを読み込み中です",
- "xpack.reporting.listing.table.maxSizeReachedTooltip": "最大サイズに達成、部分データが含まれています。",
"xpack.reporting.listing.table.noCreatedReportsDescription": "レポートが作成されていません",
"xpack.reporting.listing.table.requestFailedErrorMessage": "リクエストに失敗しました",
"xpack.reporting.listing.tableColumns.actionsTitle": "アクション",
"xpack.reporting.listing.tableColumns.createdAtTitle": "作成日時:",
"xpack.reporting.listing.tableColumns.reportTitle": "レポート",
"xpack.reporting.listing.tableColumns.statusTitle": "ステータス",
- "xpack.reporting.listing.tableValue.statusDetail.maxSizeReachedText": " - 最大サイズに達成",
- "xpack.reporting.listing.tableValue.statusDetail.pendingStatusReachedText": "保留中 - ジョブの処理持ち",
- "xpack.reporting.listing.tableValue.statusDetail.statusTimestampText": "{statusTimestamp} 時点で {statusLabel}",
- "xpack.reporting.listing.tableValue.statusDetail.warningsText": "エラー発生:詳細はジョブ情報をご覧ください。",
"xpack.reporting.management.reportingTitle": "レポート",
"xpack.reporting.panelContent.advancedOptions": "高度なオプション",
"xpack.reporting.panelContent.copyUrlButtonLabel": "POST URL をコピー",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index aad31db65b19a..97302468aae47 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -19550,9 +19550,6 @@
"xpack.reporting.diagnostic.configSizeMismatch": "xpack.reporting.{KIBANA_MAX_SIZE_BYTES_PATH} ({kibanaMaxContentBytes}) 大于 ElasticSearch 的 {ES_MAX_SIZE_BYTES_PATH} ({elasticSearchMaxContentBytes})。请在 ElasticSearch 中将 {ES_MAX_SIZE_BYTES_PATH} 设置为匹配或减小 Kibana 中的 xpack.reporting.{KIBANA_MAX_SIZE_BYTES_PATH}。",
"xpack.reporting.diagnostic.noUsableSandbox": "无法使用 Chromium 沙盒。您自行承担使用“xpack.reporting.capture.browser.chromium.disableSandbox”禁用此项的风险。请参见 {url}",
"xpack.reporting.diagnostic.screenshotFailureMessage": "我们无法拍摄 Kibana 安装的屏幕截图。",
- "xpack.reporting.errorButton.showReportErrorAriaLabel": "显示报告错误",
- "xpack.reporting.errorButton.unableToFetchReportContentTitle": "无法提取报告内容",
- "xpack.reporting.errorButton.unableToGenerateReportTitle": "无法生成报告",
"xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}",
"xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "作业标头缺失",
"xpack.reporting.exportTypes.csv.executeJob.dateFormateSetting": "Kibana 高级设置“{dateFormatTimezone}”已设置为“浏览器”。日期将格式化为 UTC 以避免混淆。",
@@ -19563,7 +19560,6 @@
"xpack.reporting.exportTypes.printablePdf.logoDescription": "由 Elastic 提供支持",
"xpack.reporting.exportTypes.printablePdf.pagingDescription": "第 {currentPage} 页,共 {pageCount} 页",
"xpack.reporting.jobsQuery.deleteError": "无法删除报告:{error}",
- "xpack.reporting.jobStatuses.cancelledText": "已取消",
"xpack.reporting.jobStatuses.completedText": "已完成",
"xpack.reporting.jobStatuses.failedText": "失败",
"xpack.reporting.jobStatuses.pendingText": "待处理",
@@ -19589,7 +19585,6 @@
"xpack.reporting.listing.reports.subtitle": "获取在 Kibana 应用程序中生成的报告。",
"xpack.reporting.listing.reportstitle": "报告",
"xpack.reporting.listing.table.captionDescription": "在 Kibana 应用程序中生成的报告",
- "xpack.reporting.listing.table.csvContainsFormulas": "您的 CSV 包含电子表格应用程序可解释为公式的字符。",
"xpack.reporting.listing.table.deleteCancelButton": "取消",
"xpack.reporting.listing.table.deleteConfim": "报告 {reportTitle} 已删除",
"xpack.reporting.listing.table.deleteConfirmButton": "删除",
@@ -19601,17 +19596,12 @@
"xpack.reporting.listing.table.downloadReport": "下载报告",
"xpack.reporting.listing.table.downloadReportAriaLabel": "下载报告",
"xpack.reporting.listing.table.loadingReportsDescription": "正在载入报告",
- "xpack.reporting.listing.table.maxSizeReachedTooltip": "已达到最大大小,包含部分数据。",
"xpack.reporting.listing.table.noCreatedReportsDescription": "未创建任何报告",
"xpack.reporting.listing.table.requestFailedErrorMessage": "请求失败",
"xpack.reporting.listing.tableColumns.actionsTitle": "操作",
"xpack.reporting.listing.tableColumns.createdAtTitle": "创建于",
"xpack.reporting.listing.tableColumns.reportTitle": "报告",
"xpack.reporting.listing.tableColumns.statusTitle": "状态",
- "xpack.reporting.listing.tableValue.statusDetail.maxSizeReachedText": " - 最大大小已达到",
- "xpack.reporting.listing.tableValue.statusDetail.pendingStatusReachedText": "待处理 - 正在等候处理作业",
- "xpack.reporting.listing.tableValue.statusDetail.statusTimestampText": "{statusTimestamp} 时为 {statusLabel}",
- "xpack.reporting.listing.tableValue.statusDetail.warningsText": "发生了错误:请参阅作业信息以了解详情。",
"xpack.reporting.management.reportingTitle": "Reporting",
"xpack.reporting.panelContent.advancedOptions": "高级选项",
"xpack.reporting.panelContent.copyUrlButtonLabel": "复制 POST URL",
diff --git a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts
index 11b5d8a51c9a8..6ed4add217ae7 100644
--- a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts
+++ b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public';
+import { esKuery } from '../../../../../src/plugins/data/public';
+import type { IndexPattern } from '../../../../../src/plugins/data/public';
import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib';
const getKueryString = (urlFilters: string): string => {
@@ -25,7 +26,7 @@ const getKueryString = (urlFilters: string): string => {
};
export const useUpdateKueryString = (
- indexPattern: IIndexPattern | null,
+ indexPattern: IndexPattern | null,
filterQueryString = '',
urlFilters: string
): [string?, Error?] => {
diff --git a/x-pack/plugins/uptime/public/state/reducers/index_pattern.ts b/x-pack/plugins/uptime/public/state/reducers/index_pattern.ts
index 3fb2ada060d65..d16860850bd78 100644
--- a/x-pack/plugins/uptime/public/state/reducers/index_pattern.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/index_pattern.ts
@@ -7,10 +7,10 @@
import { handleActions, Action } from 'redux-actions';
import { getIndexPattern, getIndexPatternSuccess, getIndexPatternFail } from '../actions';
-import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns';
+import type { IndexPattern } from '../../../../../../src/plugins/data/common/index_patterns';
export interface IndexPatternState {
- index_pattern: IIndexPattern | null;
+ index_pattern: IndexPattern | null;
errors: any[];
loading: boolean;
}
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts
index 10846442d1c84..e1cf24521d1b1 100644
--- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts
+++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts
@@ -134,7 +134,8 @@ function defineTypeWithMigration(core: CoreSetup, deps: PluginsSet
core.savedObjects.registerType({
name: SAVED_OBJECT_WITH_MIGRATION_TYPE,
hidden: false,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated', // in data.json, we simulate that existing objects were created with `namespaceType: 'single'`
+ convertToMultiNamespaceTypeVersion: '8.0.0', // in this version we convert from a single-namespace type to a "share-capable" multi-namespace isolated type
mappings: {
properties: {
nonEncryptedAttribute: {
@@ -199,6 +200,18 @@ function defineTypeWithMigration(core: CoreSetup, deps: PluginsSet
},
inputType: typePriorTo790,
}),
+
+ // NOTE FOR MAINTAINERS: do not add any more migrations before 8.0.0 unless you regenerate the test data for two of the objects in
+ // data.json: '362828f0-eef2-11eb-9073-11359682300a' and '36448a90-eef2-11eb-9073-11359682300a. These are used in the test cases 'for
+ // a saved object that does not need to be migrated before it is converted'.
+
+ // This empty migration is necessary to ensure that the saved object is decrypted with its old descriptor/ and re-encrypted with its
+ // new descriptor, if necessary. This is included because the saved object is being converted to `namespaceType: 'multiple-isolated'`
+ // in 8.0.0 (see the `convertToMultiNamespaceTypeVersion` field in the saved object type registration process).
+ '8.0.0': deps.encryptedSavedObjects.createMigration({
+ isMigrationNeededPredicate: (doc): doc is SavedObjectUnsanitizedDoc => true,
+ migration: (doc) => doc, // no-op
+ }),
},
});
}
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json
index 88ec54cdf3a54..71ac4dfc974d4 100644
--- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json
+++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json
@@ -223,6 +223,70 @@
}
}
+{
+ "type": "doc",
+ "value": {
+ "id": "custom-space:saved-object-with-migration:a67c6950-eed8-11eb-9a62-032b4e4049d1",
+ "index": ".kibana_1",
+ "source": {
+ "saved-object-with-migration": {
+ "encryptedAttribute": "BIOBsx5SjLq3ZQdOJv06XeCAMY9ZrYj8K5bcGa5+wpd3TeT2sqln1+9AGblnfxT7LXRI3sLWQ900+wRQzBhJYx8PNKH+Yw+GdeESpu73PFHdWt/52cJKr+b4EPALFc00tIMEDHdT9FyQhqJ7nV8UpwtjcuTp9SA=",
+ "nonEncryptedAttribute": "elastic"
+ },
+ "type": "saved-object-with-migration",
+ "references": [],
+ "namespace": "custom-space",
+ "migrationVersion": {
+ "saved-object-with-migration": "7.7.0"
+ },
+ "updated_at": "2021-07-27T12:46:23.881Z"
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "saved-object-with-migration:362828f0-eef2-11eb-9073-11359682300a",
+ "index": ".kibana_1",
+ "source": {
+ "saved-object-with-migration": {
+ "encryptedAttribute": "wWDAtF/5PkCb5BxjfWyRxoIoHbJXlb5cGAKg9ztZ1Bz9Zwo0/xf2yTa3Gq/CbYrvey/F9FZkZOUk03USPaqa5mfFO8FhORkfmNLQaPhgCIDNd6SbIhN8RYkqWVTYSVgcZrwes+VwiTUZ29mCJprVSHwXdyAOy4g=",
+ "nonEncryptedAttribute": "elastic-migrated",
+ "additionalEncryptedAttribute": "mszSQj0+Wv7G6kZJQsqf7CWwjJwwyriMlBcUjSHTLlj+tljbLTb7PI7gR07S9l7BXd3Lquc5PeOJifl2HvnTh8s871d/WdtIvt2K/ggwA2ae9NH6ui8A15cuPlXiGO612qccsIyBzhsftFyWJNuLBApmqeEy7HFe"
+ },
+ "type": "saved-object-with-migration",
+ "references": [],
+ "migrationVersion": {
+ "saved-object-with-migration": "7.9.0"
+ },
+ "updated_at": "2021-07-27T15:49:22.324Z"
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "custom-space:saved-object-with-migration:36448a90-eef2-11eb-9073-11359682300a",
+ "index": ".kibana_1",
+ "source": {
+ "saved-object-with-migration": {
+ "encryptedAttribute": "33lfpnBI136UfkdcQLzovzBXdUaeDouN0Z32qkVutgZJ5SU60hMtaHWXNkaU9DGy9jtr0ptwm6FCYmZbyDrlGMwyZP2n0PzMhwW9fRcBh7he12Cm1mImWTrxgYoRtc1MX20/orbINx5VnuNl1Ide7htAm1oPRjM=",
+ "nonEncryptedAttribute": "elastic-migrated",
+ "additionalEncryptedAttribute": "e2rsxBijtMGcdw7A+WAWJNlLOhQCZnEP1sdcHxVO5aQouiUVeI1OTFcOY3h/+iZBlSGvZdGRURgimrSNc0HRicemZx3o4v1gVw0JX3RRatzdl02v3GJoFzBWfQGyf3xhNNWmkweGJrFQqr2kfdKjIHbdVmMt4LZj"
+ },
+ "type": "saved-object-with-migration",
+ "references": [],
+ "namespace": "custom-space",
+ "migrationVersion": {
+ "saved-object-with-migration": "7.9.0"
+ },
+ "updated_at": "2021-07-27T15:49:22.509Z"
+ }
+ }
+}
+
{
"type": "doc",
"value": {
@@ -367,4 +431,4 @@
"updated_at": "2020-06-17T16:29:27.563Z"
}
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
index 0b01f4f385da6..311228424afe3 100644
--- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
+++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
@@ -527,20 +527,103 @@ export default function ({ getService }: FtrProviderContext) {
);
});
- it('migrates unencrypted fields on saved objects', async () => {
- const { body: decryptedResponse } = await supertest
- .get(
- `/api/saved_objects/get-decrypted-as-internal-user/saved-object-with-migration/74f3e6d7-b7bb-477d-ac28-92ee22728e6e`
- )
- .expect(200);
+ function getGetApiUrl({ objectId, spaceId }: { objectId: string; spaceId?: string }) {
+ const spacePrefix = spaceId ? `/s/${spaceId}` : '';
+ return `${spacePrefix}/api/saved_objects/get-decrypted-as-internal-user/saved-object-with-migration/${objectId}`;
+ }
+
+ // For brevity, each encrypted saved object has the same decrypted attributes after migrations/conversion.
+ // An assertion based on this ensures all encrypted fields can still be decrypted after migrations/conversion have been applied.
+ const expectedDecryptedAttributes = {
+ encryptedAttribute: 'this is my secret api key',
+ nonEncryptedAttribute: 'elastic-migrated', // this field was migrated in 7.8.0
+ additionalEncryptedAttribute: 'elastic-migrated-encrypted', // this field was added in 7.9.0
+ };
+
+ // In these test cases, we simulate a scenario where some existing objects that are migrated when Kibana starts up. Note that when a
+ // document migration is triggered, the saved object "convert" transform is also applied by the Core migration algorithm.
+ describe('handles index migration correctly', () => {
+ describe('in the default space', () => {
+ it('for a saved object that needs to be migrated before it is converted', async () => {
+ const getApiUrl = getGetApiUrl({ objectId: '74f3e6d7-b7bb-477d-ac28-92ee22728e6e' });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
+ });
+
+ it('for a saved object that does not need to be migrated before it is converted', async () => {
+ const getApiUrl = getGetApiUrl({ objectId: '362828f0-eef2-11eb-9073-11359682300a' });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
+ });
+ });
+
+ describe('in a custom space', () => {
+ const spaceId = 'custom-space';
+
+ it('for a saved object that needs to be migrated before it is converted', async () => {
+ const getApiUrl = getGetApiUrl({
+ objectId: 'a98e22f8-530e-5d69-baf7-97526796f3a6', // This ID is not found in the data.json file, it is dynamically generated when the object is converted; the original ID is a67c6950-eed8-11eb-9a62-032b4e4049d1
+ spaceId,
+ });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
+ });
+
+ it('for a saved object that does not need to be migrated before it is converted', async () => {
+ const getApiUrl = getGetApiUrl({
+ objectId: '41395c74-da7a-5679-9535-412d550a6cf7', // This ID is not found in the data.json file, it is dynamically generated when the object is converted; the original ID is 36448a90-eef2-11eb-9073-11359682300a
+ spaceId,
+ });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
+ });
+ });
+ });
+
+ // In these test cases, we simulate a scenario where new objects are migrated upon creation. This happens because an outdated
+ // `migrationVersion` field is included below. Note that when a document migration is triggered, the saved object "convert" transform
+ // is *not* applied by the Core migration algorithm.
+ describe('handles document migration correctly', () => {
+ function getCreateApiUrl({ spaceId }: { spaceId?: string } = {}) {
+ const spacePrefix = spaceId ? `/s/${spaceId}` : '';
+ return `${spacePrefix}/api/saved_objects/saved-object-with-migration`;
+ }
+
+ const objectToCreate = {
+ attributes: {
+ encryptedAttribute: 'this is my secret api key',
+ nonEncryptedAttribute: 'elastic',
+ },
+ migrationVersion: { 'saved-object-with-migration': '7.7.0' },
+ };
+
+ it('in the default space', async () => {
+ const createApiUrl = getCreateApiUrl();
+ const { body: savedObject } = await supertest
+ .post(createApiUrl)
+ .set('kbn-xsrf', 'xxx')
+ .send(objectToCreate)
+ .expect(200);
+ const { id: objectId } = savedObject;
+
+ const getApiUrl = getGetApiUrl({ objectId });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
+ });
+
+ it('in a custom space', async () => {
+ const spaceId = 'custom-space';
+ const createApiUrl = getCreateApiUrl({ spaceId });
+ const { body: savedObject } = await supertest
+ .post(createApiUrl)
+ .set('kbn-xsrf', 'xxx')
+ .send(objectToCreate)
+ .expect(200);
+ const { id: objectId } = savedObject;
- expect(decryptedResponse.attributes).to.eql({
- // ensures the encrypted field can still be decrypted after the migration
- encryptedAttribute: 'this is my secret api key',
- // ensures the non-encrypted field has been migrated in 7.8.0
- nonEncryptedAttribute: 'elastic-migrated',
- // ensures the non-encrypted field has been migrated into a new encrypted field in 7.9.0
- additionalEncryptedAttribute: 'elastic-migrated-encrypted',
+ const getApiUrl = getGetApiUrl({ objectId, spaceId });
+ const { body: decryptedResponse } = await supertest.get(getApiUrl).expect(200);
+ expect(decryptedResponse.attributes).to.eql(expectedDecryptedAttributes);
});
});
});
diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts
index d5aded01fce7b..5979ae378c22b 100644
--- a/x-pack/test/functional/apps/dashboard/index.ts
+++ b/x-pack/test/functional/apps/dashboard/index.ts
@@ -21,6 +21,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./dashboard_maps_by_value'));
loadTestFile(require.resolve('./migration_smoke_tests/lens_migration_smoke_test'));
+ loadTestFile(require.resolve('./migration_smoke_tests/visualize_migration_smoke_test'));
loadTestFile(require.resolve('./migration_smoke_tests/tsvb_migration_smoke_test'));
});
}
diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson
new file mode 100644
index 0000000000000..9b7ce6ffdd999
--- /dev/null
+++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson
@@ -0,0 +1,7 @@
+{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","title":"shakespeare"},"coreMigrationVersion":"7.12.1","id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-16T18:56:14.524Z","version":"WzM4LDFd"}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Speaker Count (Area Chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Speaker Count (Area Chart By-Ref)\",\"type\":\"area\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Line Count\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Speaker\"},\"schema\":\"segment\"}],\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Line Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Line Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}}}"},"coreMigrationVersion":"7.12.1","id":"9f90fae0-e66b-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-19T15:32:03.822Z","version":"WzU0NSwxXQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Tag Cloud","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Tag Cloud\",\"type\":\"tagcloud\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true}}"},"coreMigrationVersion":"7.12.1","id":"954e2df0-e66b-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-16T19:25:30.580Z","version":"WzIwMSwxXQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Unique Speakers by Play (Pie chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Unique Speakers by Play (Pie chart By-Ref)\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"params\":{\"field\":\"speaker\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"play_name\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}]}"},"coreMigrationVersion":"7.12.1","id":"6b818220-e66f-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-16T19:52:58.437Z","version":"WzMxMiwxXQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"speaker : \\\"HAMLET\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Hamlet Speaking Overtime (Area chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Hamlet Speaking Overtime (Area chart By-Ref)\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speech_number\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}]}"},"coreMigrationVersion":"7.12.1","id":"3b53b990-e8a6-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-19T15:30:22.251Z","version":"WzUxMSwxXQ=="}
+{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"b05dcb4e-d866-43cd-a6af-d26def3f6231\"},\"panelIndex\":\"b05dcb4e-d866-43cd-a6af-d26def3f6231\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Line Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Line Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Line Count\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Speaker\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Speaker Count (Area Chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"52747d95-33a3-4c36-b870-4a23f3e4dfec\"},\"panelIndex\":\"52747d95-33a3-4c36-b870-4a23f3e4dfec\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"d7df311d-e0cf-45f2-82cf-80b5a891fa9a\"},\"panelIndex\":\"d7df311d-e0cf-45f2-82cf-80b5a891fa9a\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"Word Cloud\",\"description\":\"\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Words (Tag Cloud By-Ref)\",\"panelRefName\":\"panel_2\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"ab307583-e63e-41fe-adc6-5be5f9b8b053\"},\"panelIndex\":\"ab307583-e63e-41fe-adc6-5be5f9b8b053\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Words (Tag Cloud By-Value)\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":30,\"w\":24,\"h\":15,\"i\":\"d6be9fb1-7f48-4175-a29a-fb80420cceb9\"},\"panelIndex\":\"d6be9fb1-7f48-4175-a29a-fb80420cceb9\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"params\":{\"field\":\"speaker\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"play_name\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"b8203dae-afe5-4d64-9c13-8ad62206b8e1\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"Shakespeare Search\",\"config\":{\"url\":{\"template\":\"https://shakespeare.folger.edu/search/?search_text={{event.value}}\"},\"openInNewTab\":true,\"encodeUrl\":true},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"title\":\"Unique Speakers by Play (Pie chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":30,\"w\":24,\"h\":15,\"i\":\"5098cff8-0d4f-4a71-8d1b-18d19a018b1f\"},\"panelIndex\":\"5098cff8-0d4f-4a71-8d1b-18d19a018b1f\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"910c4965-8781-49b9-9d6d-18ea8241a96a\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"Shakespeare Search\",\"config\":{\"url\":{\"template\":\"https://shakespeare.folger.edu/search/?search_text={{event.value}}\"},\"openInNewTab\":true,\"encodeUrl\":true},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_5\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":45,\"w\":24,\"h\":15,\"i\":\"2fc385e6-dfe5-49d6-8302-62c17d7a50a4\"},\"panelIndex\":\"2fc385e6-dfe5-49d6-8302-62c17d7a50a4\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speech_number\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"speaker : \\\"HAMLET\\\" \",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Hamlet Speaking Overtime (Area chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":45,\"w\":24,\"h\":15,\"i\":\"964c5ffe-aae3-40b1-8240-14c5a218bbe2\"},\"panelIndex\":\"964c5ffe-aae3-40b1-8240-14c5a218bbe2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_7\"}]","timeRestore":false,"title":"[7.12.1] Visualize Test Dashboard","version":1},"coreMigrationVersion":"7.12.1","id":"8a8f5a90-e668-11eb-86e8-1ffd09dc5582","migrationVersion":{"dashboard":"7.11.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"9f90fae0-e66b-11eb-86e8-1ffd09dc5582","name":"panel_1","type":"visualization"},{"id":"954e2df0-e66b-11eb-86e8-1ffd09dc5582","name":"panel_2","type":"visualization"},{"id":"6b818220-e66f-11eb-86e8-1ffd09dc5582","name":"panel_5","type":"visualization"},{"id":"3b53b990-e8a6-11eb-86e8-1ffd09dc5582","name":"panel_7","type":"visualization"}],"type":"dashboard","updated_at":"2021-07-19T15:49:46.191Z","version":"WzU4NywxXQ=="}
+{"exportedCount":6,"missingRefCount":0,"missingReferences":[]}
\ No newline at end of file
diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/visualize_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/visualize_migration_smoke_test.ts
new file mode 100644
index 0000000000000..d3d6ca46cd227
--- /dev/null
+++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/visualize_migration_smoke_test.ts
@@ -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.
+ */
+
+/* This test is importing saved objects from 7.13.0 to 8.0 and the backported version
+ * will import from 6.8.x to 7.x.x
+ */
+
+import expect from '@kbn/expect';
+import path from 'path';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const testSubjects = getService('testSubjects');
+ const dashboardPanelActions = getService('dashboardPanelActions');
+
+ const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects', 'dashboard']);
+
+ describe('Export import saved objects between versions', () => {
+ before(async () => {
+ await esArchiver.loadIfNeeded(
+ 'x-pack/test/functional/es_archives/getting_started/shakespeare'
+ );
+ await kibanaServer.uiSettings.replace({});
+ await PageObjects.settings.navigateTo();
+ await PageObjects.settings.clickKibanaSavedObjects();
+ await PageObjects.savedObjects.importFile(
+ path.join(__dirname, 'exports', 'visualize_dashboard_migration_test_7_12_1.ndjson')
+ );
+ });
+
+ after(async () => {
+ await esArchiver.unload('x-pack/test/functional/es_archives/getting_started/shakespeare');
+ await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana');
+ });
+
+ it('should be able to import dashboard with various Visualize panels from 7.12.1', async () => {
+ // this will catch cases where there is an error in the migrations.
+ await PageObjects.savedObjects.checkImportSucceeded();
+ await PageObjects.savedObjects.clickImportDone();
+ });
+
+ it('should render all panels on the dashboard', async () => {
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.loadSavedDashboard('[7.12.1] Visualize Test Dashboard');
+
+ // dashboard should load properly
+ await PageObjects.dashboard.expectOnDashboard('[7.12.1] Visualize Test Dashboard');
+ await PageObjects.dashboard.waitForRenderComplete();
+
+ // There should be 0 error embeddables on the dashboard
+ const errorEmbeddables = await testSubjects.findAll('embeddableStackError');
+ expect(errorEmbeddables.length).to.be(0);
+ });
+
+ it('should show the edit action for all panels', async () => {
+ await PageObjects.dashboard.switchToEditMode();
+
+ // All panels should be editable. This will catch cases where an error does not create an error embeddable.
+ const panelTitles = await PageObjects.dashboard.getPanelTitles();
+ for (const title of panelTitles) {
+ await dashboardPanelActions.expectExistsEditPanelAction(title);
+ }
+ });
+
+ it('should retain all panel drilldowns from 7.12.1', async () => {
+ // Both panels configured with drilldowns in 7.12.1 should still have drilldowns.
+ const totalPanels = await PageObjects.dashboard.getPanelCount();
+ let panelsWithDrilldowns = 0;
+ for (let panelIndex = 0; panelIndex < totalPanels; panelIndex++) {
+ if ((await PageObjects.dashboard.getPanelDrilldownCount(panelIndex)) === 1) {
+ panelsWithDrilldowns++;
+ }
+ }
+ expect(panelsWithDrilldowns).to.be(2);
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/reporting_management/report_listing.ts b/x-pack/test/functional/apps/reporting_management/report_listing.ts
index 0b1fce3700986..eb2e339e9be66 100644
--- a/x-pack/test/functional/apps/reporting_management/report_listing.ts
+++ b/x-pack/test/functional/apps/reporting_management/report_listing.ts
@@ -6,6 +6,7 @@
*/
import expect from '@kbn/expect';
+import { REPORT_TABLE_ID } from '../../../../plugins/reporting/common/constants';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
@@ -37,7 +38,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// to reset the data after deletion testing
await esArchiver.load('x-pack/test/functional/es_archives/reporting/archived_reports');
await pageObjects.common.navigateToApp('reporting');
- await testSubjects.existOrFail('reportJobListing', { timeout: 200000 });
+ await testSubjects.existOrFail(REPORT_TABLE_ID, { timeout: 200000 });
});
after(async () => {
@@ -52,7 +53,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('Confirm single report deletion works', async () => {
log.debug('Checking for reports.');
await retry.try(async () => {
- await testSubjects.click('checkboxSelectRow-k9a9xlwl0gpe1457b10rraq3');
+ await testSubjects.click('checkboxSelectRow-krb7arhe164k0763b50bjm29');
});
const deleteButton = await testSubjects.find('deleteReportButton');
await retry.waitFor('delete button to become enabled', async () => {
@@ -62,7 +63,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.exists('confirmModalBodyText');
await testSubjects.click('confirmModalConfirmButton');
await retry.try(async () => {
- await testSubjects.waitForDeleted('checkboxSelectRow-k9a9xlwl0gpe1457b10rraq3');
+ await testSubjects.waitForDeleted('checkboxSelectRow-krb7arhe164k0763b50bjm29');
});
});
@@ -71,10 +72,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const previousButton = await testSubjects.find('pagination-button-previous');
expect(await previousButton.getAttribute('disabled')).to.be('true');
- await testSubjects.find('checkboxSelectRow-k9a9xlwl0gpe1457b10rraq3'); // find first row of page 1
+ await testSubjects.find('checkboxSelectRow-krb7arhe164k0763b50bjm29'); // find first row of page 1
await testSubjects.click('pagination-button-1'); // click page 2
- await testSubjects.find('checkboxSelectRow-k9a9uc4x0gpe1457b16wthc8'); // wait for first row of page 2
+ await testSubjects.find('checkboxSelectRow-kraz0qle154g0763b569zz83'); // wait for first row of page 2
await testSubjects.click('pagination-button-2'); // click page 3
await testSubjects.find('checkboxSelectRow-k9a9p1840gpe1457b1ghfxw5'); // wait for first row of page 3
@@ -82,5 +83,73 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// previous CAN be clicked
expect(await previousButton.getAttribute('disabled')).to.be(null);
});
+
+ it('Displays types of report jobs', async () => {
+ const list = await pageObjects.reporting.getManagementList();
+ expectSnapshot(list).toMatchInline(`
+ Array [
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 10:29 PMtest_user",
+ "report": "Automated reportsearch",
+ "status": "Completed at 2021-07-19 @ 10:29 PMSee report info for warnings.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:47 PMtest_user",
+ "report": "Discover search [2021-07-19T11:47:35.995-07:00]search",
+ "status": "Completed at 2021-07-19 @ 06:47 PM",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:46 PMtest_user",
+ "report": "Discover search [2021-07-19T11:46:00.132-07:00]search",
+ "status": "Completed at 2021-07-19 @ 06:46 PMSee report info for warnings.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:44 PMtest_user",
+ "report": "Discover search [2021-07-19T11:44:48.670-07:00]search",
+ "status": "Completed at 2021-07-19 @ 06:44 PMSee report info for warnings.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:41 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Pending at 2021-07-19 @ 06:41 PMWaiting for job to be processed.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:41 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Failed at 2021-07-19 @ 06:43 PMSee report info for error details.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:41 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Completed at 2021-07-19 @ 06:41 PMSee report info for warnings.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:38 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Completed at 2021-07-19 @ 06:39 PMSee report info for warnings.",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:38 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Completed at 2021-07-19 @ 06:39 PM",
+ },
+ Object {
+ "actions": "",
+ "createdAt": "2021-07-19 @ 06:38 PMtest_user",
+ "report": "[Flights] Global Flight Dashboarddashboard",
+ "status": "Completed at 2021-07-19 @ 06:38 PM",
+ },
+ ]
+ `);
+ });
});
};
diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts
index d8867527ba2ac..a6b3e8e41be99 100644
--- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts
+++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts
@@ -578,6 +578,7 @@ export default function ({ getService }: FtrProviderContext) {
await transform.table.assertTransformRowFields(testData.transformId, {
id: testData.transformId,
description: testData.transformDescription,
+ type: testData.type,
status: testData.expected.row.status,
mode: testData.expected.row.mode,
progress: testData.expected.row.progress,
diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts
index e5944c7f12578..b79bef82267d7 100644
--- a/x-pack/test/functional/apps/transform/creation_saved_search.ts
+++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts
@@ -279,6 +279,7 @@ export default function ({ getService }: FtrProviderContext) {
await transform.table.assertTransformRowFields(testData.transformId, {
id: testData.transformId,
description: testData.transformDescription,
+ type: testData.type,
status: testData.expected.row.status,
mode: testData.expected.row.mode,
progress: testData.expected.row.progress,
diff --git a/x-pack/test/functional/apps/transform/deleting.ts b/x-pack/test/functional/apps/transform/deleting.ts
index 68530c586b6e2..c86171cdb1d6f 100644
--- a/x-pack/test/functional/apps/transform/deleting.ts
+++ b/x-pack/test/functional/apps/transform/deleting.ts
@@ -24,6 +24,7 @@ export default function ({ getService }: FtrProviderContext) {
expected: {
row: {
status: TRANSFORM_STATE.STOPPED,
+ type: 'pivot',
mode: 'batch',
progress: 100,
},
@@ -35,6 +36,7 @@ export default function ({ getService }: FtrProviderContext) {
expected: {
row: {
status: TRANSFORM_STATE.STOPPED,
+ type: 'pivot',
mode: 'continuous',
progress: undefined,
},
@@ -50,6 +52,7 @@ export default function ({ getService }: FtrProviderContext) {
messageText: 'updated transform.',
row: {
status: TRANSFORM_STATE.STOPPED,
+ type: 'latest',
mode: 'batch',
progress: 100,
},
@@ -106,6 +109,7 @@ export default function ({ getService }: FtrProviderContext) {
await transform.table.assertTransformRowFields(testData.originalConfig.id, {
id: testData.originalConfig.id,
description: testData.originalConfig.description,
+ type: testData.expected.row.type,
status: testData.expected.row.status,
mode: testData.expected.row.mode,
progress: testData.expected.row.progress,
diff --git a/x-pack/test/functional/apps/transform/editing.ts b/x-pack/test/functional/apps/transform/editing.ts
index 01f93d9e8c0a8..993c239a04304 100644
--- a/x-pack/test/functional/apps/transform/editing.ts
+++ b/x-pack/test/functional/apps/transform/editing.ts
@@ -70,6 +70,7 @@ export default function ({ getService }: FtrProviderContext) {
messageText: 'updated transform.',
row: {
status: TRANSFORM_STATE.STOPPED,
+ type: 'pivot',
mode: 'batch',
progress: '100',
},
@@ -85,6 +86,7 @@ export default function ({ getService }: FtrProviderContext) {
messageText: 'updated transform.',
row: {
status: TRANSFORM_STATE.STOPPED,
+ type: 'latest',
mode: 'batch',
progress: '100',
},
@@ -170,6 +172,7 @@ export default function ({ getService }: FtrProviderContext) {
await transform.table.assertTransformRowFields(testData.originalConfig.id, {
id: testData.originalConfig.id,
description: testData.transformDescription,
+ type: testData.expected.row.type,
status: testData.expected.row.status,
mode: testData.expected.row.mode,
progress: testData.expected.row.progress,
diff --git a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
index 3bced4fca9b40..d50943fad991a 100644
--- a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
+++ b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
@@ -13,8 +13,7 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const transform = getService('transform');
- // FLAKY: https://github.com/elastic/kibana/issues/107043
- describe.skip('for user with full transform access', function () {
+ describe('for user with full transform access', function () {
describe('with no data loaded', function () {
before(async () => {
await transform.securityUI.loginAsTransformPowerUser();
diff --git a/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts b/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts
index 5634ed4736e4d..6a04d33ff152d 100644
--- a/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts
+++ b/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts
@@ -13,8 +13,7 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const transform = getService('transform');
- // FLAKY: https://github.com/elastic/kibana/issues/107043
- describe.skip('for user with full transform access', function () {
+ describe('for user with read only transform access', function () {
describe('with no data loaded', function () {
before(async () => {
await transform.securityUI.loginAsTransformViewer();
diff --git a/x-pack/test/functional/es_archives/reporting/archived_reports/data.json.gz b/x-pack/test/functional/es_archives/reporting/archived_reports/data.json.gz
index 34a30bd84a592..22423aa1fc99f 100644
Binary files a/x-pack/test/functional/es_archives/reporting/archived_reports/data.json.gz and b/x-pack/test/functional/es_archives/reporting/archived_reports/data.json.gz differ
diff --git a/x-pack/test/functional/es_archives/reporting/archived_reports/mappings.json b/x-pack/test/functional/es_archives/reporting/archived_reports/mappings.json
index 3c3225a70d47f..b2363174e92ec 100644
--- a/x-pack/test/functional/es_archives/reporting/archived_reports/mappings.json
+++ b/x-pack/test/functional/es_archives/reporting/archived_reports/mappings.json
@@ -4,6 +4,561 @@
"aliases": {
},
"index": ".reporting-2020.04.19",
+ "mappings": {
+ "properties": {
+ "attempts": {
+ "type": "long"
+ },
+ "browser_type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "completed_at": {
+ "type": "date"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "jobtype": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "kibana_id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "kibana_name": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "max_attempts": {
+ "type": "long"
+ },
+ "meta": {
+ "properties": {
+ "layout": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "objectType": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "output": {
+ "properties": {
+ "content": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "content_type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "csv_contains_formulas": {
+ "type": "boolean"
+ },
+ "max_size_reached": {
+ "type": "boolean"
+ },
+ "size": {
+ "type": "long"
+ }
+ }
+ },
+ "payload": {
+ "properties": {
+ "basePath": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "browserTimezone": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "fields": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "forceNow": {
+ "type": "date"
+ },
+ "headers": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "indexPatternId": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "indexPatternSavedObject": {
+ "properties": {
+ "attributes": {
+ "properties": {
+ "fieldFormatMap": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "fields": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "timeFieldName": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "title": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "migrationVersion": {
+ "properties": {
+ "index-pattern": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "version": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "layout": {
+ "properties": {
+ "dimensions": {
+ "properties": {
+ "height": {
+ "type": "long"
+ },
+ "width": {
+ "type": "long"
+ }
+ }
+ },
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "metaFields": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "objectType": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "relativeUrl": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "relativeUrls": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "searchRequest": {
+ "properties": {
+ "body": {
+ "properties": {
+ "_source": {
+ "type": "object"
+ },
+ "docvalue_fields": {
+ "properties": {
+ "field": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "format": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "query": {
+ "properties": {
+ "bool": {
+ "properties": {
+ "filter": {
+ "properties": {
+ "match_all": {
+ "type": "object"
+ },
+ "range": {
+ "properties": {
+ "order_date": {
+ "properties": {
+ "format": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "gte": {
+ "type": "date"
+ },
+ "lte": {
+ "type": "date"
+ }
+ }
+ },
+ "timestamp": {
+ "properties": {
+ "format": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "gte": {
+ "type": "date"
+ },
+ "lte": {
+ "type": "date"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "script_fields": {
+ "properties": {
+ "hour_of_day": {
+ "properties": {
+ "script": {
+ "properties": {
+ "lang": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "source": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "sort": {
+ "properties": {
+ "order_date": {
+ "properties": {
+ "order": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "unmapped_type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "timestamp": {
+ "properties": {
+ "order": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "unmapped_type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ }
+ }
+ },
+ "stored_fields": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "version": {
+ "type": "boolean"
+ }
+ }
+ },
+ "index": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "type": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "priority": {
+ "type": "long"
+ },
+ "process_expiration": {
+ "type": "date"
+ },
+ "started_at": {
+ "type": "date"
+ },
+ "status": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "timeout": {
+ "type": "long"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
+
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ },
+ "index": ".reporting-2021-07-18",
"mappings": {
"properties": {
"attempts": {
@@ -55,6 +610,9 @@
}
}
},
+ "migration_version": {
+ "type": "keyword"
+ },
"output": {
"properties": {
"content": {
@@ -72,6 +630,15 @@
},
"size": {
"type": "long"
+ },
+ "warnings": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
}
}
},
@@ -104,4 +671,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/test/functional/page_objects/reporting_page.ts b/x-pack/test/functional/page_objects/reporting_page.ts
index 742d41031004b..302e71304869b 100644
--- a/x-pack/test/functional/page_objects/reporting_page.ts
+++ b/x-pack/test/functional/page_objects/reporting_page.ts
@@ -10,6 +10,7 @@ import { format as formatUrl } from 'url';
import supertestAsPromised from 'supertest-as-promised';
import { FtrService } from '../ftr_provider_context';
+import { REPORT_TABLE_ID, REPORT_TABLE_ROW_ID } from '../../../plugins/reporting/common/constants';
export class ReportingPageObject extends FtrService {
private readonly browser = this.ctx.getService('browser');
@@ -157,4 +158,21 @@ export class ReportingPageObject extends FtrService {
const toTime = 'Sep 23, 1999 @ 18:31:44.000';
await this.timePicker.setAbsoluteRange(fromTime, toTime);
}
+
+ async getManagementList() {
+ const table = await this.testSubjects.find(REPORT_TABLE_ID);
+ const allRows = await table.findAllByTestSubject(REPORT_TABLE_ROW_ID);
+
+ return await Promise.all(
+ allRows.map(async (row) => {
+ const $ = await row.parseDomContent();
+ return {
+ report: $.findTestSubject('reportingListItemObjectTitle').text().trim(),
+ createdAt: $.findTestSubject('reportJobCreatedAt').text().trim(),
+ status: $.findTestSubject('reportJobStatus').text().trim(),
+ actions: $.findTestSubject('reportJobActions').text().trim(),
+ };
+ })
+ );
+ }
}
diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts
index 7c4a45fb601ea..9e3ffcdfd8095 100644
--- a/x-pack/test/functional/services/transform/transform_table.ts
+++ b/x-pack/test/functional/services/transform/transform_table.ts
@@ -37,6 +37,11 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
.find('.euiTableCellContent')
.text()
.trim(),
+ type: $tr
+ .findTestSubject('transformListColumnType')
+ .find('.euiTableCellContent')
+ .text()
+ .trim(),
status: $tr
.findTestSubject('transformListColumnStatus')
.find('.euiTableCellContent')
@@ -190,38 +195,54 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
});
}
- public async assertTransformExpandedRow() {
- await testSubjects.click('transformListRowDetailsToggle');
+ public async ensureDetailsOpen() {
+ await retry.tryForTime(30 * 1000, async () => {
+ if (!(await testSubjects.exists('transformExpandedRowTabbedContent'))) {
+ await testSubjects.click('transformListRowDetailsToggle');
+ await testSubjects.existOrFail('transformExpandedRowTabbedContent', { timeout: 1000 });
+ }
+ });
+ }
- // The expanded row should show the details tab content by default
- await testSubjects.existOrFail('transformDetailsTab');
- await testSubjects.existOrFail('~transformDetailsTabContent');
+ public async ensureDetailsClosed() {
+ await retry.tryForTime(30 * 1000, async () => {
+ if (await testSubjects.exists('transformExpandedRowTabbedContent')) {
+ await testSubjects.click('transformListRowDetailsToggle');
+ await testSubjects.missingOrFail('transformExpandedRowTabbedContent', { timeout: 1000 });
+ }
+ });
+ }
- // Walk through the rest of the tabs and check if the corresponding content shows up
- await testSubjects.existOrFail('transformJsonTab');
- await testSubjects.click('transformJsonTab');
- await testSubjects.existOrFail('~transformJsonTabContent');
+ public async switchToExpandedRowTab(tabSubject: string, contentSubject: string) {
+ await retry.tryForTime(30 * 1000, async () => {
+ await testSubjects.click(tabSubject);
+ await testSubjects.existOrFail(contentSubject, { timeout: 1000 });
+ });
+ }
- await testSubjects.existOrFail('transformMessagesTab');
- await testSubjects.click('transformMessagesTab');
- await testSubjects.existOrFail('~transformMessagesTabContent');
+ public async assertTransformExpandedRow() {
+ await this.ensureDetailsOpen();
+ await retry.tryForTime(30 * 1000, async () => {
+ // The expanded row should show the details tab content by default
+ await testSubjects.existOrFail('transformDetailsTab', { timeout: 1000 });
+ await testSubjects.existOrFail('~transformDetailsTabContent', { timeout: 1000 });
+ });
- await testSubjects.existOrFail('transformPreviewTab');
- await testSubjects.click('transformPreviewTab');
- await testSubjects.existOrFail('~transformPivotPreview');
+ // Walk through the rest of the tabs and check if the corresponding content shows up
+ await this.switchToExpandedRowTab('transformJsonTab', '~transformJsonTabContent');
+ await this.switchToExpandedRowTab('transformMessagesTab', '~transformMessagesTabContent');
+ await this.switchToExpandedRowTab('transformPreviewTab', '~transformPivotPreview');
}
public async assertTransformExpandedRowMessages(expectedText: string) {
- await testSubjects.click('transformListRowDetailsToggle');
+ await this.ensureDetailsOpen();
// The expanded row should show the details tab content by default
await testSubjects.existOrFail('transformDetailsTab');
await testSubjects.existOrFail('~transformDetailsTabContent');
// Click on the messages tab and assert the messages
- await testSubjects.existOrFail('transformMessagesTab');
- await testSubjects.click('transformMessagesTab');
- await testSubjects.existOrFail('~transformMessagesTabContent');
+ await this.switchToExpandedRowTab('transformMessagesTab', '~transformMessagesTabContent');
await retry.tryForTime(30 * 1000, async () => {
const actualText = await testSubjects.getVisibleText('~transformMessagesTabContent');
expect(actualText.toLowerCase()).to.contain(