From bd7f7ab48a1538fd6651e527b3cbe3216ec41995 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 20 Jan 2020 15:37:41 +0100 Subject: [PATCH 01/59] [ML] Single Metric Viewer: Fix job check. (#55191) Fixes switching via navigation to Single Metric Viewer from Anomaly Explorer for a job which isn't supported in the Single Metric Viewer. --- .../routing/routes/timeseriesexplorer.tsx | 33 ++- .../{index.js => index.ts} | 0 ...s => timeseriesexplorer_no_jobs_found.tsx} | 0 .../timeseriesexplorer.d.ts | 7 +- .../timeseriesexplorer/timeseriesexplorer.js | 243 +++--------------- .../timeseriesexplorer_page.tsx | 54 ++++ .../timeseriesexplorer_utils/index.ts | 1 + .../validate_job_selection.ts | 87 +++++++ 8 files changed, 206 insertions(+), 219 deletions(-) rename x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/{index.js => index.ts} (100%) rename x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/{timeseriesexplorer_no_jobs_found.js => timeseriesexplorer_no_jobs_found.tsx} (100%) create mode 100644 x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx create mode 100644 x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index c3c644d43fa593..f824faf7845c64 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -26,7 +26,10 @@ import { APP_STATE_ACTION } from '../../timeseriesexplorer/timeseriesexplorer_co import { createTimeSeriesJobData, getAutoZoomDuration, + validateJobSelection, } from '../../timeseriesexplorer/timeseriesexplorer_utils'; +import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page'; +import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found'; import { useUrlState } from '../../util/url_state'; import { useTableInterval } from '../../components/controls/select_interval'; import { useTableSeverity } from '../../components/controls/select_severity'; @@ -81,6 +84,7 @@ export const TimeSeriesExplorerUrlStateManager: FC(); const refresh = useRefresh(); useEffect(() => { @@ -141,6 +145,10 @@ export const TimeSeriesExplorerUrlStateManager: FC + + + ); + } + + if (selectedJobId === undefined || autoZoomDuration === undefined || bounds === undefined) { + return null; + } + return ( void; - autoZoomDuration?: number; - bounds?: TimeRangeBounds; + autoZoomDuration: number; + bounds: TimeRangeBounds; dateFormatTz: string; - jobsWithTimeRange: any[]; lastRefresh: number; - selectedJobIds: string[]; + selectedJobId: string; selectedDetectorIndex: number; selectedEntities: any[]; selectedForecastId: string; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 1862ead0457431..f3d8692bfb3e97 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -8,7 +8,7 @@ * React component for rendering Single Metric Viewer. */ -import { debounce, difference, each, find, get, has, isEqual, without } from 'lodash'; +import { debounce, each, find, get, has, isEqual } from 'lodash'; import moment from 'moment-timezone'; import { Subject, Subscription, forkJoin } from 'rxjs'; import { map, debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -24,7 +24,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiProgress, EuiSelect, EuiSpacer, EuiText, @@ -49,15 +48,12 @@ import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; import { ChartTooltip } from '../components/chart_tooltip'; import { EntityControl } from './components/entity_control'; import { ForecastingModal } from './components/forecasting_modal/forecasting_modal'; -import { JobSelector } from '../components/job_selector'; -import { getTimeRangeFromSelection } from '../components/job_selector/job_select_service_utils'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; -import { NavigationMenu } from '../components/navigation_menu'; import { SelectInterval } from '../components/controls/select_interval/select_interval'; import { SelectSeverity } from '../components/controls/select_severity/select_severity'; import { TimeseriesChart } from './components/timeseries_chart/timeseries_chart'; -import { TimeseriesexplorerNoJobsFound } from './components/timeseriesexplorer_no_jobs_found'; import { TimeseriesexplorerNoChartData } from './components/timeseriesexplorer_no_chart_data'; +import { TimeSeriesExplorerPage } from './timeseriesexplorer_page'; import { ml } from '../services/ml_api_service'; import { mlFieldFormatService } from '../services/field_format_service'; @@ -154,44 +150,16 @@ function getTimeseriesexplorerDefaultState() { }; } -const TimeSeriesExplorerPage = ({ children, jobSelectorProps, loading, resizeRef }) => ( - - - {/* Show animated progress bar while loading */} - {loading && } - {/* Show a progress bar with progress 0% when not loading. - If we'd just show no progress bar when not loading it would result in a flickering height effect. */} - {!loading && ( - - )} - -
- {children} -
-
-); - const containerPadding = 24; export class TimeSeriesExplorer extends React.Component { static propTypes = { appStateHandler: PropTypes.func.isRequired, - autoZoomDuration: PropTypes.number, - bounds: PropTypes.object, + autoZoomDuration: PropTypes.number.isRequired, + bounds: PropTypes.object.isRequired, dateFormatTz: PropTypes.string.isRequired, - jobsWithTimeRange: PropTypes.array.isRequired, lastRefresh: PropTypes.number.isRequired, - selectedJobIds: PropTypes.arrayOf(PropTypes.string), + selectedJobId: PropTypes.string.isRequired, selectedDetectorIndex: PropTypes.number, selectedEntities: PropTypes.object, selectedForecastId: PropTypes.string, @@ -285,9 +253,9 @@ export class TimeSeriesExplorer extends React.Component { contextChartSelectedInitCallDone = false; getFocusAggregationInterval(selection) { - const { selectedJobIds } = this.props; + const { selectedJobId } = this.props; const jobs = createTimeSeriesJobData(mlJobService.jobs); - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); // Calculate the aggregation interval for the focus chart. const bounds = { min: moment(selection.from), max: moment(selection.to) }; @@ -299,9 +267,9 @@ export class TimeSeriesExplorer extends React.Component { * Gets focus data for the current component state/ */ getFocusData(selection) { - const { selectedJobIds, selectedForecastId, selectedDetectorIndex } = this.props; + const { selectedJobId, selectedForecastId, selectedDetectorIndex } = this.props; const { modelPlotEnabled } = this.state; - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); // Calculate the aggregation interval for the focus chart. @@ -356,11 +324,11 @@ export class TimeSeriesExplorer extends React.Component { const { dateFormatTz, selectedDetectorIndex, - selectedJobIds, + selectedJobId, tableInterval, tableSeverity, } = this.props; - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); return ml.results @@ -424,8 +392,8 @@ export class TimeSeriesExplorer extends React.Component { loadEntityValues = async (entities, searchTerm = {}) => { this.setState({ entitiesLoading: true }); - const { bounds, selectedJobIds, selectedDetectorIndex } = this.props; - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const { bounds, selectedJobId, selectedDetectorIndex } = this.props; + const selectedJob = mlJobService.getJob(selectedJobId); // Populate the entity input datalists with the values from the top records by score // for the selected detector across the full time range. No need to pass through finish(). @@ -479,17 +447,13 @@ export class TimeSeriesExplorer extends React.Component { bounds, selectedDetectorIndex, selectedForecastId, - selectedJobIds, + selectedJobId, zoom, } = this.props; - if (selectedJobIds === undefined || bounds === undefined) { - return; - } - const { loadCounter: currentLoadCounter } = this.state; - const currentSelectedJob = mlJobService.getJob(selectedJobIds[0]); + const currentSelectedJob = mlJobService.getJob(selectedJobId); if (currentSelectedJob === undefined) { return; @@ -526,7 +490,7 @@ export class TimeSeriesExplorer extends React.Component { const { loadCounter, modelPlotEnabled } = this.state; const jobs = createTimeSeriesJobData(mlJobService.jobs); - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); const detectorIndex = selectedDetectorIndex; let awaitingCount = 3; @@ -717,8 +681,8 @@ export class TimeSeriesExplorer extends React.Component { * @param callback to invoke after a state update. */ getControlsForDetector = () => { - const { selectedDetectorIndex, selectedEntities, selectedJobIds } = this.props; - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const { selectedDetectorIndex, selectedEntities, selectedJobId } = this.props; + const selectedJob = mlJobService.getJob(selectedJobId); const entities = []; @@ -871,9 +835,9 @@ export class TimeSeriesExplorer extends React.Component { } }), switchMap(selection => { - const { selectedJobIds } = this.props; + const { selectedJobId } = this.props; const jobs = createTimeSeriesJobData(mlJobService.jobs); - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); // Calculate the aggregation interval for the focus chart. const bounds = { min: moment(selection.from), max: moment(selection.to) }; @@ -927,133 +891,19 @@ export class TimeSeriesExplorer extends React.Component { this.componentDidUpdate(); } - /** - * returns true/false if setGlobalState has been triggered - * or returns the job id which should be loaded. - */ - checkJobSelection() { - const { jobsWithTimeRange, selectedJobIds, setGlobalState } = this.props; - - const jobs = createTimeSeriesJobData(mlJobService.jobs); - const timeSeriesJobIds = jobs.map(j => j.id); - - // Check if any of the jobs set in the URL are not time series jobs - // (e.g. if switching to this view straight from the Anomaly Explorer). - const invalidIds = difference(selectedJobIds, timeSeriesJobIds); - const validSelectedJobIds = without(selectedJobIds, ...invalidIds); - if (invalidIds.length > 0) { - let warningText = i18n.translate( - 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage', - { - defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`, - values: { - invalidIdsCount: invalidIds.length, - invalidIds, - }, - } - ); - if (validSelectedJobIds.length === 0 && timeSeriesJobIds.length > 0) { - warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', { - defaultMessage: ', auto selecting first job', - }); - } - toastNotifications.addWarning(warningText); - } - - if (validSelectedJobIds.length > 1) { - // if more than one job or a group has been loaded from the URL - if (validSelectedJobIds.length > 1) { - // if more than one job, select the first job from the selection. - toastNotifications.addWarning( - i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { - defaultMessage: 'You can only view one job at a time in this dashboard', - }) - ); - setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); - return true; - } else { - // if a group has been loaded - if (selectedJobIds.length > 0) { - // if the group contains valid jobs, select the first - toastNotifications.addWarning( - i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { - defaultMessage: 'You can only view one job at a time in this dashboard', - }) - ); - setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); - return true; - } else if (jobs.length > 0) { - // if there are no valid jobs in the group but there are valid jobs - // in the list of all jobs, select the first - const jobIds = [jobs[0].id]; - const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds); - setGlobalState({ - ...{ ml: { jobIds } }, - ...(time !== undefined ? { time } : {}), - }); - return true; - } else { - // if there are no valid jobs left. - return false; - } - } - } else if (invalidIds.length > 0 && validSelectedJobIds.length > 0) { - // if some ids have been filtered out because they were invalid. - // refresh the URL with the first valid id - setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); - return true; - } else if (validSelectedJobIds.length > 0) { - // normal behavior. a job ID has been loaded from the URL - // Clear the detectorIndex, entities and forecast info. - return validSelectedJobIds[0]; - } else { - if (validSelectedJobIds.length === 0 && jobs.length > 0) { - // no jobs were loaded from the URL, so add the first job - // from the full jobs list. - const jobIds = [jobs[0].id]; - const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds); - setGlobalState({ - ...{ ml: { jobIds } }, - ...(time !== undefined ? { time } : {}), - }); - return true; - } else { - // Jobs exist, but no time series jobs. - return false; - } - } - } - componentDidUpdate(previousProps) { - if ( - previousProps === undefined || - !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) - ) { - const update = this.checkJobSelection(); - // - true means a setGlobalState got triggered and - // we'll just wait for the next React render. - // - false means there are either no jobs or no time based jobs present. - // - if we get back a string it means we got back a job id we can load. - if (update === true) { - return; - } else if (update === false) { - this.setState({ loading: false }); - return; - } else if (typeof update === 'string') { - this.contextChartSelectedInitCallDone = false; - this.setState({ fullRefresh: false, loading: true }, () => { - this.loadForJobId(update); - }); - } + if (previousProps === undefined || previousProps.selectedJobId !== this.props.selectedJobId) { + this.contextChartSelectedInitCallDone = false; + this.setState({ fullRefresh: false, loading: true }, () => { + this.loadForJobId(this.props.selectedJobId); + }); } if ( - this.props.bounds !== undefined && - this.props.selectedJobIds !== undefined && - (previousProps === undefined || - !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) || - previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || - !isEqual(previousProps.selectedEntities, this.props.selectedEntities)) + previousProps === undefined || + previousProps.selectedJobId !== this.props.selectedJobId || + previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities) ) { const entityControls = this.getControlsForDetector(); this.loadEntityValues(entityControls); @@ -1076,7 +926,7 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || !isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) || - !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) || + previousProps.selectedJobId !== this.props.selectedJobId || !isEqual(previousProps.zoom, this.props.zoom) ) { const fullRefresh = @@ -1086,7 +936,7 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || !isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) || - !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds); + previousProps.selectedJobId !== this.props.selectedJobId; this.loadSingleMetricData(fullRefresh); } @@ -1159,7 +1009,7 @@ export class TimeSeriesExplorer extends React.Component { dateFormatTz, lastRefresh, selectedDetectorIndex, - selectedJobIds, + selectedJobId, } = this.props; const { @@ -1211,34 +1061,13 @@ export class TimeSeriesExplorer extends React.Component { autoZoomDuration, }; - const jobSelectorProps = { - dateFormatTz, - singleSelection: true, - timeseriesOnly: true, - }; - const jobs = createTimeSeriesJobData(mlJobService.jobs); - if (jobs.length === 0) { - return ( - - - - ); - } - - if ( - selectedJobIds === undefined || - selectedJobIds.length > 1 || - selectedDetectorIndex === undefined || - mlJobService.getJob(selectedJobIds[0]) === undefined - ) { - return ( - - ); + if (selectedDetectorIndex === undefined || mlJobService.getJob(selectedJobId) === undefined) { + return ; } - const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); const fieldNamesWithEmptyValues = entityControls @@ -1280,7 +1109,7 @@ export class TimeSeriesExplorer extends React.Component { return ( diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx new file mode 100644 index 00000000000000..9da1a79232fce5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiProgress } from '@elastic/eui'; + +import { JobSelector } from '../components/job_selector'; +import { NavigationMenu } from '../components/navigation_menu'; + +interface TimeSeriesExplorerPageProps { + dateFormatTz: string; + loading?: boolean; + resizeRef?: any; +} + +export const TimeSeriesExplorerPage: FC = ({ + children, + dateFormatTz, + loading, + resizeRef, +}) => { + return ( + <> + + {/* Show animated progress bar while loading */} + {loading === true && ( + + )} + {/* Show a progress bar with progress 0% when not loading. + If we'd just show no progress bar when not loading it would result in a flickering height effect. */} + {loading === false && ( + + )} + +
+ {children} +
+ + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts index 578dbdf1277a05..dcfbe94c97cc6e 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts @@ -6,3 +6,4 @@ export { getFocusData } from './get_focus_data'; export * from './timeseriesexplorer_utils'; +export { validateJobSelection } from './validate_job_selection'; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts new file mode 100644 index 00000000000000..f1cdaf3ba8c1bc --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { difference, without } from 'lodash'; + +import { i18n } from '@kbn/i18n'; + +import { toastNotifications } from 'ui/notify'; + +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; + +import { getTimeRangeFromSelection } from '../../components/job_selector/job_select_service_utils'; +import { mlJobService } from '../../services/job_service'; + +import { createTimeSeriesJobData } from './timeseriesexplorer_utils'; + +/** + * returns true/false if setGlobalState has been triggered + * or returns the job id which should be loaded. + */ +export function validateJobSelection( + jobsWithTimeRange: MlJobWithTimeRange[], + selectedJobIds: string[], + setGlobalState: (...args: any) => void +) { + const jobs = createTimeSeriesJobData(mlJobService.jobs); + const timeSeriesJobIds: string[] = jobs.map((j: any) => j.id); + + // Check if any of the jobs set in the URL are not time series jobs + // (e.g. if switching to this view straight from the Anomaly Explorer). + const invalidIds: string[] = difference(selectedJobIds, timeSeriesJobIds); + const validSelectedJobIds = without(selectedJobIds, ...invalidIds); + if (invalidIds.length > 0) { + let warningText = i18n.translate( + 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage', + { + defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`, + values: { + invalidIdsCount: invalidIds.length, + invalidIds: invalidIds.join(', '), + }, + } + ); + if (validSelectedJobIds.length === 0 && timeSeriesJobIds.length > 0) { + warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', { + defaultMessage: ', auto selecting first job', + }); + } + toastNotifications.addWarning(warningText); + } + + if (validSelectedJobIds.length > 1) { + // if more than one job, select the first job from the selection. + toastNotifications.addWarning( + i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { + defaultMessage: 'You can only view one job at a time in this dashboard', + }) + ); + setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); + return true; + } else if (invalidIds.length > 0 && validSelectedJobIds.length > 0) { + // if some ids have been filtered out because they were invalid. + // refresh the URL with the first valid id + setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); + return true; + } else if (validSelectedJobIds.length === 1) { + // normal behavior. a job ID has been loaded from the URL + // Clear the detectorIndex, entities and forecast info. + return validSelectedJobIds[0]; + } else if (validSelectedJobIds.length === 0 && jobs.length > 0) { + // no jobs were loaded from the URL, so add the first job + // from the full jobs list. + const jobIds = [jobs[0].id]; + const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds); + setGlobalState({ + ...{ ml: { jobIds } }, + ...(time !== undefined ? { time } : {}), + }); + return true; + } else { + // Jobs exist, but no time series jobs. + return false; + } +} From 3e567b5cf217f3ca5292f386587085f06898147e Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 20 Jan 2020 15:43:22 +0100 Subject: [PATCH 02/59] [ML] Correctly pass on severity value to anomaly explorer charts. (#55207) - Fixes passing on the severity value correctly to anomaly explorer charts. The wrong value of undefined being passed down caused anomaly markers not showing up. - This bug surfaced that the severity value was never applied to filter multi-bucket anomalies which is now also fixed by this PR. - Adds a check if topInfluencers is an array. --- .../ml/public/application/explorer/explorer.d.ts | 1 + .../ml/public/application/explorer/explorer.js | 4 ++-- .../explorer_charts/explorer_chart_single_metric.js | 11 ++++++----- .../ml/public/application/explorer/explorer_utils.js | 12 +++++++----- .../public/application/routing/routes/explorer.tsx | 1 + 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts index b8df021990f584..a85674986c7f7f 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts @@ -15,6 +15,7 @@ import { AppStateSelectedCells } from '../explorer/explorer_utils'; declare interface ExplorerProps { explorerState: ExplorerState; + severity: number; showCharts: boolean; setSelectedCells: (swimlaneSelectedCells: AppStateSelectedCells) => void; } diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js index 79071319965781..6a1c5339de1f56 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js @@ -96,6 +96,7 @@ export class Explorer extends React.Component { static propTypes = { explorerState: PropTypes.object.isRequired, setSelectedCells: PropTypes.func.isRequired, + severity: PropTypes.number.isRequired, showCharts: PropTypes.bool.isRequired, }; @@ -260,7 +261,7 @@ export class Explorer extends React.Component { }; render() { - const { showCharts } = this.props; + const { showCharts, severity } = this.props; const { annotationsData, @@ -276,7 +277,6 @@ export class Explorer extends React.Component { queryString, selectedCells, selectedJobs, - severity, swimlaneContainerWidth, tableData, tableQueryString, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 583375c87007e4..a255b6b0434e4e 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -53,7 +53,7 @@ export const ExplorerChartSingleMetric = injectI18n( static propTypes = { tooManyBuckets: PropTypes.bool, seriesConfig: PropTypes.object, - severity: PropTypes.number, + severity: PropTypes.number.isRequired, }; componentDidMount() { @@ -312,13 +312,16 @@ export const ExplorerChartSingleMetric = injectI18n( }) .on('mouseout', () => mlChartTooltipService.hide()); + const isAnomalyVisible = d => + _.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity; + // Update all dots to new positions. dots .attr('cx', d => lineChartXScale(d.date)) .attr('cy', d => lineChartYScale(d.value)) .attr('class', d => { let markerClass = 'metric-value'; - if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity) { + if (isAnomalyVisible(d)) { markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`; } return markerClass; @@ -328,9 +331,7 @@ export const ExplorerChartSingleMetric = injectI18n( const multiBucketMarkers = lineChartGroup .select('.chart-markers') .selectAll('.multi-bucket') - .data( - data.filter(d => d.anomalyScore !== null && showMultiBucketAnomalyMarker(d) === true) - ); + .data(data.filter(d => isAnomalyVisible(d) && showMultiBucketAnomalyMarker(d) === true)); // Remove multi-bucket markers that are no longer needed multiBucketMarkers.exit().remove(); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js index 4fb4e7d4df94f2..14d356c0d1c81c 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js @@ -280,11 +280,13 @@ export function loadViewByTopFieldValuesForSelectedTime( const topFieldValues = []; const topInfluencers = resp.influencers[viewBySwimlaneFieldName]; - topInfluencers.forEach(influencerData => { - if (influencerData.maxAnomalyScore > 0) { - topFieldValues.push(influencerData.influencerFieldValue); - } - }); + if (Array.isArray(topInfluencers)) { + topInfluencers.forEach(influencerData => { + if (influencerData.maxAnomalyScore > 0) { + topFieldValues.push(influencerData.influencerFieldValue); + } + }); + } resolve(topFieldValues); }); } else { diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx index 6aaad5294369b7..633efc2856dac1 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx @@ -184,6 +184,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim explorerState, setSelectedCells, showCharts, + severity: tableSeverity.val, }} /> From bf0bcfe7035f71d321f75abbd83b8ca0e4625d79 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 20 Jan 2020 16:12:25 +0000 Subject: [PATCH 03/59] [ML] Fixing missing job_type in job messages search (#55330) --- .../services/job_messages_service.js | 193 ------------------ .../job_audit_messages/job_audit_messages.js | 31 ++- 2 files changed, 26 insertions(+), 198 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/public/application/services/job_messages_service.js diff --git a/x-pack/legacy/plugins/ml/public/application/services/job_messages_service.js b/x-pack/legacy/plugins/ml/public/application/services/job_messages_service.js deleted file mode 100644 index 1b0151897d1a6a..00000000000000 --- a/x-pack/legacy/plugins/ml/public/application/services/job_messages_service.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// Service for carrying out Elasticsearch queries to obtain data for the -// Ml Results dashboards. -import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; -import { ml } from '../services/ml_api_service'; - -// filter to match job_type: 'anomaly_detector' or no job_type field at all -// if no job_type field exist, we can assume the message is for an anomaly detector job -const anomalyDetectorTypeFilter = { - bool: { - should: [ - { - term: { - job_type: 'anomaly_detector', - }, - }, - { - bool: { - must_not: { - exists: { - field: 'job_type', - }, - }, - }, - }, - ], - minimum_should_match: 1, - }, -}; - -// search for audit messages, jobId is optional. -// without it, all jobs will be listed. -// fromRange should be a string formatted in ES time units. e.g. 12h, 1d, 7d -function getJobAuditMessages(fromRange, jobId) { - return new Promise((resolve, reject) => { - let jobFilter = {}; - // if no jobId specified, load all of the messages - if (jobId !== undefined) { - jobFilter = { - bool: { - should: [ - { - term: { - job_id: '', // catch system messages - }, - }, - { - term: { - job_id: jobId, // messages for specified jobId - }, - }, - ], - }, - }; - } - - let timeFilter = {}; - if (fromRange !== undefined && fromRange !== '') { - timeFilter = { - range: { - timestamp: { - gte: `now-${fromRange}`, - lte: 'now', - }, - }, - }; - } - - ml.esSearch({ - index: ML_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - rest_total_hits_as_int: true, - size: 1000, - body: { - sort: [{ timestamp: { order: 'asc' } }, { job_id: { order: 'asc' } }], - query: { - bool: { - filter: [ - { - bool: { - must_not: { - term: { - level: 'activity', - }, - }, - }, - }, - anomalyDetectorTypeFilter, - jobFilter, - timeFilter, - ], - }, - }, - }, - }) - .then(resp => { - let messages = []; - if (resp.hits.total !== 0) { - messages = resp.hits.hits.map(hit => hit._source); - } - resolve({ messages }); - }) - .catch(resp => { - reject(resp); - }); - }); -} - -// search highest, most recent audit messages for all jobs for the last 24hrs. -function getAuditMessagesSummary() { - return new Promise((resolve, reject) => { - ml.esSearch({ - index: ML_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - rest_total_hits_as_int: true, - size: 0, - body: { - query: { - bool: { - filter: [ - { - range: { - timestamp: { - gte: 'now-1d', - }, - }, - }, - anomalyDetectorTypeFilter, - ], - }, - }, - aggs: { - levelsPerJob: { - terms: { - field: 'job_id', - }, - aggs: { - levels: { - terms: { - field: 'level', - }, - aggs: { - latestMessage: { - terms: { - field: 'message.raw', - size: 1, - order: { - latestMessage: 'desc', - }, - }, - aggs: { - latestMessage: { - max: { - field: 'timestamp', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }) - .then(resp => { - let messagesPerJob = []; - if ( - resp.hits.total !== 0 && - resp.aggregations && - resp.aggregations.levelsPerJob && - resp.aggregations.levelsPerJob.buckets && - resp.aggregations.levelsPerJob.buckets.length - ) { - messagesPerJob = resp.aggregations.levelsPerJob.buckets; - } - resolve({ messagesPerJob }); - }) - .catch(resp => { - reject(resp); - }); - }); -} - -export const jobMessagesService = { - getJobAuditMessages, - getAuditMessagesSummary, -}; diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index c9cc8a3da574ad..52495b3b732d0a 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -10,6 +10,30 @@ import moment from 'moment'; const SIZE = 1000; const LEVEL = { system_info: -1, info: 0, warning: 1, error: 2 }; +// filter to match job_type: 'anomaly_detector' or no job_type field at all +// if no job_type field exist, we can assume the message is for an anomaly detector job +const anomalyDetectorTypeFilter = { + bool: { + should: [ + { + term: { + job_type: 'anomaly_detector', + }, + }, + { + bool: { + must_not: { + exists: { + field: 'job_type', + }, + }, + }, + }, + ], + minimum_should_match: 1, + }, +}; + export function jobAuditMessagesProvider(callWithRequest) { // search for audit messages, // jobId is optional. without it, all jobs will be listed. @@ -47,13 +71,9 @@ export function jobAuditMessagesProvider(callWithRequest) { level: 'activity', }, }, - must: { - term: { - job_type: 'anomaly_detector', - }, - }, }, }, + anomalyDetectorTypeFilter, timeFilter, ], }, @@ -119,6 +139,7 @@ export function jobAuditMessagesProvider(callWithRequest) { }, }, }, + anomalyDetectorTypeFilter, ], }, }; From 638792a5571d79ee26744b91a047eb4857e203b0 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 20 Jan 2020 17:24:17 +0000 Subject: [PATCH 04/59] removes CTA from Task Manager info message (#55334) removes CTA from Task Manager info message --- x-pack/plugins/task_manager/server/task_manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index 93e98f33a30b04..da9640fa3e0718 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -401,7 +401,7 @@ export async function claimAvailableTasks( } else { performance.mark('claimAvailableTasks.noAvailableWorkers'); logger.info( - `[Task Ownership]: Task Manager has skipped Claiming Ownership of available tasks at it has ran out Available Workers. If this happens often, consider adjusting the "xpack.task_manager.max_workers" configuration.` + `[Task Ownership]: Task Manager has skipped Claiming Ownership of available tasks at it has ran out Available Workers.` ); } return []; From cdb0021ac6bdcc162232a5453feebd1c3693c6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez?= Date: Mon, 20 Jan 2020 18:28:55 +0100 Subject: [PATCH 05/59] [Logs UI] Fix z-index of logs page toolbar (#54469) * Fix z-index of logs page toolbar * Extract `FixedDatePicker` from log setup page, and use it in the stream page * Clean unused import Co-authored-by: Elastic Machine --- .../public/components/fixed_datepicker.tsx | 25 +++++++++++++++++++ .../analysis_setup_timerange_form.tsx | 20 +-------------- .../components/logging/log_time_controls.tsx | 3 ++- 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx diff --git a/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx b/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx new file mode 100644 index 00000000000000..aab1bcd1da8730 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiDatePicker, EuiDatePickerProps } from '@elastic/eui'; +import euiStyled from '../../../../common/eui_styled_components'; + +export const FixedDatePicker = euiStyled( + ({ + className, + inputClassName, + ...datePickerProps + }: { + className?: string; + inputClassName?: string; + } & EuiDatePickerProps) => ( + + ) +)` + z-index: 3 !important; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx index 4319f844b1dcc8..02119fd1c09dd2 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx @@ -5,8 +5,6 @@ */ import { - EuiDatePicker, - EuiDatePickerProps, EuiDescribedFormGroup, EuiFlexGroup, EuiFormControlLayout, @@ -16,8 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import moment, { Moment } from 'moment'; import React, { useMemo } from 'react'; - -import { euiStyled } from '../../../../../../../common/eui_styled_components'; +import { FixedDatePicker } from '../../../fixed_datepicker'; const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', { defaultMessage: 'Start time', @@ -138,18 +135,3 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{ ); }; - -const FixedDatePicker = euiStyled( - ({ - className, - inputClassName, - ...datePickerProps - }: { - className?: string; - inputClassName?: string; - } & EuiDatePickerProps) => ( - - ) -)` - z-index: 3 !important; -`; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx index 8f5705a9b9c563..5095edd4c715c5 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import moment, { Moment } from 'moment'; import React from 'react'; +import { FixedDatePicker } from '../fixed_datepicker'; const noop = () => undefined; @@ -56,7 +57,7 @@ export class LogTimeControls extends React.PureComponent { return ( - Date: Tue, 21 Jan 2020 14:27:51 +0530 Subject: [PATCH 06/59] [Mappings editor] Add missing max_shingle_size parameter to search_as_you_type (#55161) --- .../document_fields/field_parameters/index.ts | 2 + .../max_shingle_size_parameter.tsx | 45 +++++++++++++++++++ .../fields/field_types/search_as_you_type.tsx | 8 +++- .../constants/parameters_definition.tsx | 11 +++++ .../lib/mappings_validator.test.ts | 2 + .../app/components/mappings_editor/types.ts | 3 +- 6 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts index 9622466ad795cf..b248776c884f12 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -51,3 +51,5 @@ export * from './fielddata_parameter'; export * from './split_queries_on_whitespace_parameter'; export * from './locale_parameter'; + +export * from './max_shingle_size_parameter'; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx new file mode 100644 index 00000000000000..bc1917b2da966b --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { getFieldConfig } from '../../../lib'; +import { EditFieldFormRow } from '../fields/edit_field'; +import { UseField, Field } from '../../../shared_imports'; + +interface Props { + defaultToggleValue: boolean; +} + +export const MaxShingleSizeParameter = ({ defaultToggleValue }: Props) => ( + + + +); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx index 83541ec982ee68..dafbebd24b3fa7 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx @@ -14,6 +14,7 @@ import { NormsParameter, SimilarityParameter, TermVectorParameter, + MaxShingleSizeParameter, } from '../../field_parameters'; import { BasicParametersSection, AdvancedParametersSection } from '../edit_field'; @@ -24,7 +25,8 @@ interface Props { const getDefaultToggleValue = (param: string, field: FieldType) => { switch (param) { case 'similarity': - case 'term_vector': { + case 'term_vector': + case 'max_shingle_size': { return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue; } case 'analyzers': { @@ -47,6 +49,10 @@ export const SearchAsYouType = React.memo(({ field }: Props) => { + + { enable_position_increments: [], depth_limit: true, dims: false, + max_shingle_size: 'string_not_allowed', }, // All the parameters in "goodField" have the correct format // and should still be there after the validation ran. @@ -307,6 +308,7 @@ describe('Properties validator', () => { enable_position_increments: true, depth_limit: 20, dims: 'abc', + max_shingle_size: 2, }, }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts index 0fce3422344bc2..b7bf4e6b112d37 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts @@ -119,7 +119,8 @@ export type ParameterName = | 'points_only' | 'path' | 'dims' - | 'depth_limit'; + | 'depth_limit' + | 'max_shingle_size'; export interface Parameter { fieldConfig: FieldConfig; From 4ca2fbdb114a2e43db740eb8192857aa0981ca83 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 21 Jan 2020 11:03:55 +0200 Subject: [PATCH 07/59] [BUG] Data fetching twice on discover timefilter change (#55279) * Fix bug #54887 - Filters are not only fetch once on timefilter change - Make sure that discover doesn't fetch data when a disabled filter is changed - Support compareFilters on an array of filters. - Added tests to compare filters - Exctracted sortFilters and added tests to it. * code review + FilterCompareOptions * Remove sort by Co-authored-by: Elastic Machine --- .../filter_manager/filter_state_manager.ts | 19 ++- .../discover/np_ready/angular/discover.js | 2 +- .../query/filter_manager/filter_manager.ts | 18 +-- .../lib/compare_filters.test.ts | 131 +++++++++++++++++- .../filter_manager/lib/compare_filters.ts | 78 ++++++++--- .../query/filter_manager/lib/dedup_filters.ts | 4 +- .../query/filter_manager/lib/only_disabled.ts | 5 +- .../filter_manager/lib/sort_filters.test.ts | 91 ++++++++++++ .../query/filter_manager/lib/sort_filters.ts | 42 ++++++ 9 files changed, 351 insertions(+), 39 deletions(-) create mode 100644 src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts create mode 100644 src/plugins/data/public/query/filter_manager/lib/sort_filters.ts diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts index 61821b7ad45e94..633b7e630700d2 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts @@ -21,6 +21,13 @@ import _ from 'lodash'; import { State } from 'ui/state_management/state'; import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; +import { + compareFilters, + COMPARE_ALL_OPTIONS, + // this whole file will soon be deprecated by new state management. + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/query/filter_manager/lib/compare_filters'; + type GetAppStateFunc = () => State | undefined | null; /** @@ -63,8 +70,16 @@ export class FilterStateManager { const globalFilters = this.globalState.filters || []; const appFilters = (appState && appState.filters) || []; - const globalFilterChanged = !_.isEqual(this.filterManager.getGlobalFilters(), globalFilters); - const appFilterChanged = !_.isEqual(this.filterManager.getAppFilters(), appFilters); + const globalFilterChanged = !compareFilters( + this.filterManager.getGlobalFilters(), + globalFilters, + COMPARE_ALL_OPTIONS + ); + const appFilterChanged = !compareFilters( + this.filterManager.getAppFilters(), + appFilters, + COMPARE_ALL_OPTIONS + ); const filterStateChanged = globalFilterChanged || appFilterChanged; if (!filterStateChanged) return; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 2927565e61dce9..2bf554860f7434 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -637,7 +637,7 @@ function discoverController( // fetch data when filters fire fetch event subscriptions.add( - subscribeWithScope($scope, filterManager.getUpdates$(), { + subscribeWithScope($scope, filterManager.getFetches$(), { next: $scope.fetch, }) ); diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index c25e5df2b168a2..f7d0dddd5bf039 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -22,7 +22,8 @@ import { Subject } from 'rxjs'; import { IUiSettingsClient } from 'src/core/public'; -import { compareFilters } from './lib/compare_filters'; +import { compareFilters, COMPARE_ALL_OPTIONS } from './lib/compare_filters'; +import { sortFilters } from './lib/sort_filters'; import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; import { uniqFilters } from './lib/uniq_filters'; import { onlyDisabledFiltersChanged } from './lib/only_disabled'; @@ -76,20 +77,9 @@ export class FilterManager { } private handleStateUpdate(newFilters: esFilters.Filter[]) { - // global filters should always be first - - newFilters.sort(({ $state: a }: esFilters.Filter, { $state: b }: esFilters.Filter): number => { - if (a!.store === b!.store) { - return 0; - } else { - return a!.store === esFilters.FilterStateStore.GLOBAL_STATE && - b!.store !== esFilters.FilterStateStore.GLOBAL_STATE - ? -1 - : 1; - } - }); + newFilters.sort(sortFilters); - const filtersUpdated = !_.isEqual(this.filters, newFilters); + const filtersUpdated = !compareFilters(this.filters, newFilters, COMPARE_ALL_OPTIONS); const updatedOnlyDisabledFilters = onlyDisabledFiltersChanged(newFilters, this.filters); this.filters = newFilters; diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts index 34fd662c4ba460..9cc5938750c4ed 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { compareFilters } from './compare_filters'; +import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters'; import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { @@ -83,5 +83,134 @@ describe('filter manager utilities', () => { expect(compareFilters(f1, f2)).toBeTruthy(); }); + + test('should compare filters array to non array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], f1)).toBeFalsy(); + }); + + test('should compare filters array to partial array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], [f1])).toBeFalsy(); + }); + + test('should compare filters array to exact array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], [f1, f2])).toBeTruthy(); + }); + + test('should compare array of duplicates, ignoring meta attributes', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ); + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index2', + '' + ); + + expect(compareFilters([f1], [f2])).toBeTruthy(); + }); + + test('should compare array of duplicates, ignoring $state attributes', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + expect(compareFilters([f1], [f2])).toBeTruthy(); + }); + + test('should compare duplicates with COMPARE_ALL_OPTIONS should check store', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); + }); + + test('should compare duplicates with COMPARE_ALL_OPTIONS should not check key and value ', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + f2.meta.key = 'wassup'; + f2.meta.value = 'dog'; + + expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeTruthy(); + }); }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts index 9b171ab0aacb2a..218b9d492b61f6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts @@ -17,32 +17,64 @@ * under the License. */ -import { defaults, isEqual, omit } from 'lodash'; +import { defaults, isEqual, omit, map } from 'lodash'; import { esFilters } from '../../../../common'; +export interface FilterCompareOptions { + disabled?: boolean; + negate?: boolean; + state?: boolean; +} + +/** + * Include disabled, negate and store when comparing filters + */ +export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + disabled: true, + negate: true, + state: true, +}; + +const mapFilter = ( + filter: esFilters.Filter, + comparators: FilterCompareOptions, + excludedAttributes: string[] +) => { + const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes); + + if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); + if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); + + return cleaned; +}; + +const mapFilterArray = ( + filters: esFilters.Filter[], + comparators: FilterCompareOptions, + excludedAttributes: string[] +) => { + return map(filters, (filter: esFilters.Filter) => + mapFilter(filter, comparators, excludedAttributes) + ); +}; + /** - * Compare two filters to see if they match + * Compare two filters or filter arrays to see if they match. + * For filter arrays, the assumption is they are sorted. * - * @param {object} first The first filter to compare - * @param {object} second The second filter to compare - * @param {object} comparatorOptions Parameters to use for comparison + * @param {esFilters.Filter | esFilters.Filter[]} first The first filter or filter array to compare + * @param {esFilters.Filter | esFilters.Filter[]} second The second filter or filter array to compare + * @param {FilterCompareOptions} comparatorOptions Parameters to use for comparison * * @returns {bool} Filters are the same */ export const compareFilters = ( - first: esFilters.Filter, - second: esFilters.Filter, - comparatorOptions: any = {} + first: esFilters.Filter | esFilters.Filter[], + second: esFilters.Filter | esFilters.Filter[], + comparatorOptions: FilterCompareOptions = {} ) => { - let comparators: any = {}; - const mapFilter = (filter: esFilters.Filter) => { - const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes); - - if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); - if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); + let comparators: FilterCompareOptions = {}; - return cleaned; - }; const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { @@ -53,5 +85,17 @@ export const compareFilters = ( if (!comparators.state) excludedAttributes.push('$state'); - return isEqual(mapFilter(first), mapFilter(second)); + if (Array.isArray(first) && Array.isArray(second)) { + return isEqual( + mapFilterArray(first, comparators, excludedAttributes), + mapFilterArray(second, comparators, excludedAttributes) + ); + } else if (!Array.isArray(first) && !Array.isArray(second)) { + return isEqual( + mapFilter(first, comparators, excludedAttributes), + mapFilter(second, comparators, excludedAttributes) + ); + } else { + return false; + } }; diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts index 6dae14f480b4f1..897a645e87b5a7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts @@ -18,7 +18,7 @@ */ import { filter, find } from 'lodash'; -import { compareFilters } from './compare_filters'; +import { compareFilters, FilterCompareOptions } from './compare_filters'; import { esFilters } from '../../../../common'; /** @@ -33,7 +33,7 @@ import { esFilters } from '../../../../common'; export const dedupFilters = ( existingFilters: esFilters.Filter[], filters: esFilters.Filter[], - comparatorOptions: any = {} + comparatorOptions: FilterCompareOptions = {} ) => { if (!Array.isArray(filters)) { filters = [filters]; diff --git a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts index c040d2f2960c75..3f35c94a3f858f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts @@ -17,8 +17,9 @@ * under the License. */ -import { filter, isEqual } from 'lodash'; +import { filter } from 'lodash'; import { esFilters } from '../../../../common'; +import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters'; const isEnabled = (f: esFilters.Filter) => f && f.meta && !f.meta.disabled; @@ -35,5 +36,5 @@ export const onlyDisabledFiltersChanged = ( const newEnabledFilters = filter(newFilters || [], isEnabled); const oldEnabledFilters = filter(oldFilters || [], isEnabled); - return isEqual(oldEnabledFilters, newEnabledFilters); + return compareFilters(oldEnabledFilters, newEnabledFilters, COMPARE_ALL_OPTIONS); }; diff --git a/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts new file mode 100644 index 00000000000000..949c57e43ce743 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { sortFilters } from './sort_filters'; +import { esFilters } from '../../../../common'; + +describe('sortFilters', () => { + describe('sortFilters()', () => { + test('Not sort two application level filters', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f1); + }); + + test('Not sort two global level filters', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f1); + }); + + test('Move global level filter to the beginning of the array', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f2); + }); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts new file mode 100644 index 00000000000000..657c80fe0ccb23 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { esFilters } from '../../../../common'; + +/** + * Sort filters according to their store - global filters go first + * + * @param {object} first The first filter to compare + * @param {object} second The second filter to compare + * + * @returns {number} Sorting order of filters + */ +export const sortFilters = ( + { $state: a }: esFilters.Filter, + { $state: b }: esFilters.Filter +): number => { + if (a!.store === b!.store) { + return 0; + } else { + return a!.store === esFilters.FilterStateStore.GLOBAL_STATE && + b!.store !== esFilters.FilterStateStore.GLOBAL_STATE + ? -1 + : 1; + } +}; From 4971a2c772317ed8092ca8f35e539fe71b828b3b Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 21 Jan 2020 12:58:40 +0100 Subject: [PATCH 08/59] Expose fatalErrors API from the Start contract (#55300) * Expose FatalErrors from the Start contract. This API is intended to be used for runtime as well. * update docs * update data plugin snapshot to fix tests * address comments Co-authored-by: Elastic Machine --- ...ana-plugin-public.chromenavlinks.update.md | 60 +++++++++---------- ...ana-plugin-public.corestart.fatalerrors.md | 13 ++++ .../public/kibana-plugin-public.corestart.md | 1 + .../kibana-plugin-public.fatalerrorsstart.md | 13 ++++ .../core/public/kibana-plugin-public.md | 1 + src/core/public/core_system.ts | 2 + .../fatal_errors/fatal_errors_service.mock.ts | 4 ++ .../fatal_errors/fatal_errors_service.tsx | 23 ++++++- src/core/public/fatal_errors/index.ts | 2 +- src/core/public/index.ts | 5 +- src/core/public/legacy/legacy_service.test.ts | 2 + src/core/public/mocks.ts | 1 + src/core/public/plugins/plugin_context.ts | 1 + .../public/plugins/plugins_service.test.ts | 1 + src/core/public/public.api.md | 5 ++ .../query_string_input.test.tsx.snap | 24 ++++++++ 16 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md create mode 100644 docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md index d1cd2d3b049501..155d149f334a17 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md @@ -1,30 +1,30 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) - -## ChromeNavLinks.update() method - -> Warning: This API is now obsolete. -> -> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. -> - -Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. - -Signature: - -```typescript -update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| id | string | | -| values | ChromeNavLinkUpdateableFields | | - -Returns: - -`ChromeNavLink | undefined` - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) + +## ChromeNavLinks.update() method + +> Warning: This API is now obsolete. +> +> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. +> + +Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. + +Signature: + +```typescript +update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | +| values | ChromeNavLinkUpdateableFields | | + +Returns: + +`ChromeNavLink | undefined` + diff --git a/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md b/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md new file mode 100644 index 00000000000000..540b17b5a6f0b3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [fatalErrors](./kibana-plugin-public.corestart.fatalerrors.md) + +## CoreStart.fatalErrors property + +[FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) + +Signature: + +```typescript +fatalErrors: FatalErrorsStart; +``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index e561ee313f1000..83af82d590c364 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -19,6 +19,7 @@ export interface CoreStart | [application](./kibana-plugin-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | [chrome](./kibana-plugin-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-public.chromestart.md) | | [docLinks](./kibana-plugin-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | +| [fatalErrors](./kibana-plugin-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) | | [http](./kibana-plugin-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-public.httpstart.md) | | [i18n](./kibana-plugin-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-public.i18nstart.md) | | [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | diff --git a/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md b/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md new file mode 100644 index 00000000000000..a8ece7dcb7e02d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) + +## FatalErrorsStart type + +FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. + +Signature: + +```typescript +export declare type FatalErrorsStart = FatalErrorsSetup; +``` diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 27ca9f2d9fd577..27037d46926c18 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -129,6 +129,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | | [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | | [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | +| [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | | [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | | [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | | [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 5b31c740518e4a..97bd49416f7052 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -214,6 +214,7 @@ export class CoreSystem { const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup! }); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); + const fatalErrors = await this.fatalErrors.start(); await this.integrations.start({ uiSettings }); const coreUiTargetDomElement = document.createElement('div'); @@ -271,6 +272,7 @@ export class CoreSystem { notifications, overlays, uiSettings, + fatalErrors, }; const plugins = await this.plugins.start(core); diff --git a/src/core/public/fatal_errors/fatal_errors_service.mock.ts b/src/core/public/fatal_errors/fatal_errors_service.mock.ts index dd7702a7ee7dd9..6d9876f787fa97 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.mock.ts +++ b/src/core/public/fatal_errors/fatal_errors_service.mock.ts @@ -26,18 +26,22 @@ const createSetupContractMock = () => { return setupContract; }; +const createStartContractMock = createSetupContractMock; type FatalErrorsServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), + start: jest.fn(), }; mocked.setup.mockReturnValue(createSetupContractMock()); + mocked.start.mockReturnValue(createStartContractMock()); return mocked; }; export const fatalErrorsServiceMock = { create: createMock, createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, }; diff --git a/src/core/public/fatal_errors/fatal_errors_service.tsx b/src/core/public/fatal_errors/fatal_errors_service.tsx index 5c6a7bb322ae1f..309f07859ef264 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.tsx +++ b/src/core/public/fatal_errors/fatal_errors_service.tsx @@ -54,9 +54,18 @@ export interface FatalErrorsSetup { get$: () => Rx.Observable; } +/** + * FatalErrors stop the Kibana Public Core and displays a fatal error screen + * with details about the Kibana build and the error. + * + * @public + */ +export type FatalErrorsStart = FatalErrorsSetup; + /** @interal */ export class FatalErrorsService { private readonly errorInfo$ = new Rx.ReplaySubject(); + private fatalErrors?: FatalErrorsSetup; /** * @@ -82,7 +91,7 @@ export class FatalErrorsService { }, }); - const fatalErrorsSetup: FatalErrorsSetup = { + this.fatalErrors = { add: (error, source?) => { const errorInfo = getErrorInfo(error, source); @@ -101,9 +110,17 @@ export class FatalErrorsService { }, }; - this.setupGlobalErrorHandlers(fatalErrorsSetup); + this.setupGlobalErrorHandlers(this.fatalErrors!); - return fatalErrorsSetup; + return this.fatalErrors!; + } + + public start() { + const { fatalErrors } = this; + if (!fatalErrors) { + throw new Error('FatalErrorsService#setup() must be invoked before start.'); + } + return fatalErrors; } private renderError(injectedMetadata: InjectedMetadataSetup, i18n: I18nStart) { diff --git a/src/core/public/fatal_errors/index.ts b/src/core/public/fatal_errors/index.ts index e37a36152cf91b..c8ea1c0bccd227 100644 --- a/src/core/public/fatal_errors/index.ts +++ b/src/core/public/fatal_errors/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { FatalErrorsSetup, FatalErrorsService } from './fatal_errors_service'; +export { FatalErrorsSetup, FatalErrorsStart, FatalErrorsService } from './fatal_errors_service'; export { FatalErrorInfo } from './get_error_info'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 5b17eccc37f8b8..5e732dd05e6168 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -55,7 +55,7 @@ import { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, } from './chrome'; -import { FatalErrorsSetup, FatalErrorInfo } from './fatal_errors'; +import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; import { I18nStart } from './i18n'; import { InjectedMetadataSetup, InjectedMetadataStart, LegacyNavLink } from './injected_metadata'; @@ -232,6 +232,8 @@ export interface CoreStart { overlays: OverlayStart; /** {@link IUiSettingsClient} */ uiSettings: IUiSettingsClient; + /** {@link FatalErrorsStart} */ + fatalErrors: FatalErrorsStart; /** * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done * use *only* to retrieve config values. There is no way to set injected values @@ -302,6 +304,7 @@ export { DocLinksStart, FatalErrorInfo, FatalErrorsSetup, + FatalErrorsStart, HttpSetup, HttpStart, I18nStart, diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index 9dd24f9e4a7a3c..d08c8b52e39c9b 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -98,6 +98,7 @@ const notificationsStart = notificationServiceMock.createStartContract(); const overlayStart = overlayServiceMock.createStartContract(); const uiSettingsStart = uiSettingsServiceMock.createStartContract(); const savedObjectsStart = savedObjectsMock.createStartContract(); +const fatalErrorsStart = fatalErrorsServiceMock.createStartContract(); const mockStorage = { getItem: jest.fn() } as any; const defaultStartDeps = { @@ -112,6 +113,7 @@ const defaultStartDeps = { overlays: overlayStart, uiSettings: uiSettingsStart, savedObjects: savedObjectsStart, + fatalErrors: fatalErrorsStart, }, lastSubUrlStorage: mockStorage, targetDomElement: document.createElement('div'), diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 43c8aa6f1d6b96..ce90d49065ad4e 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -74,6 +74,7 @@ function createCoreStartMock({ basePath = '' } = {}) { injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, + fatalErrors: fatalErrorsServiceMock.createStartContract(), }; return mock; diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index f146c2452868b4..48100cba4f26e0 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -151,5 +151,6 @@ export function createPluginStartContext< injectedMetadata: { getInjectedVar: deps.injectedMetadata.getInjectedVar, }, + fatalErrors: deps.fatalErrors, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index cafc7e5887e385..dbbcda8d60e128 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -111,6 +111,7 @@ describe('PluginsService', () => { overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsMock.createStartContract(), + fatalErrors: fatalErrorsServiceMock.createStartContract(), }; mockStartContext = { ...mockStartDeps, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index abd39e864bd302..f7b260c68ee96b 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -377,6 +377,8 @@ export interface CoreStart { // (undocumented) docLinks: DocLinksStart; // (undocumented) + fatalErrors: FatalErrorsStart; + // (undocumented) http: HttpStart; // (undocumented) i18n: I18nStart; @@ -532,6 +534,9 @@ export interface FatalErrorsSetup { get$: () => Rx.Observable; } +// @public +export type FatalErrorsStart = FatalErrorsSetup; + // @public export type HandlerContextType> = T extends HandlerFunction ? U : never; diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index bc08c87304fcab..d8db53d4c6020b 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -304,6 +304,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -930,6 +934,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -1538,6 +1546,10 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -2161,6 +2173,10 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -2769,6 +2785,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -3392,6 +3412,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { From 27c8a4bc25badb3ee3c1feaa7e1e68b10bab1ccd Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 21 Jan 2020 13:04:49 +0100 Subject: [PATCH 09/59] [State Management] remove AppState from Dashboard app (#54105) Removes AppState from dashboard app and replaces it with state containers and state syncing utilities. --- .../filter_manager/filter_state_manager.ts | 14 +- .../public/dashboard/__tests__/index.ts | 1 - .../kibana/public/dashboard/legacy_imports.ts | 6 - .../public/dashboard/np_ready/application.ts | 7 - .../dashboard/np_ready/dashboard_app.tsx | 16 +- .../np_ready/dashboard_app_controller.tsx | 110 ++++++--- .../np_ready/dashboard_state.test.ts | 13 +- .../np_ready/dashboard_state_manager.ts | 233 ++++++++++++------ .../public/dashboard/np_ready/legacy_app.js | 12 +- .../np_ready/lib/migrate_app_state.test.ts | 35 +-- .../np_ready/lib/migrate_app_state.ts | 15 +- .../dashboard/np_ready/lib/save_dashboard.ts | 11 +- .../np_ready/lib/update_saved_dashboard.ts | 4 +- .../kibana/public/dashboard/np_ready/types.ts | 43 ++-- src/legacy/ui/public/chrome/api/controls.ts | 2 + .../ui/public/chrome/directives/kbn_chrome.js | 10 + .../ui/public/state_management/state.js | 2 +- .../kibana_utils/public/history/index.ts | 20 ++ .../public/history/remove_query_param.test.ts | 75 ++++++ .../public/history/remove_query_param.ts} | 43 ++-- src/plugins/kibana_utils/public/index.ts | 2 + .../url/kbn_url_storage.test.ts | 54 +++- .../state_management/url/kbn_url_storage.ts | 41 ++- .../utils/diff_object.test.ts | 0 .../state_management/utils/diff_object.ts | 0 .../public/state_sync/state_sync.test.ts | 36 +++ .../public/state_sync/state_sync.ts | 14 +- .../create_kbn_url_state_storage.test.ts | 17 +- .../create_kbn_url_state_storage.ts | 17 +- .../apps/dashboard/dashboard_clone.js | 2 +- 30 files changed, 573 insertions(+), 282 deletions(-) create mode 100644 src/plugins/kibana_utils/public/history/index.ts create mode 100644 src/plugins/kibana_utils/public/history/remove_query_param.test.ts rename src/{legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts => plugins/kibana_utils/public/history/remove_query_param.ts} (53%) rename src/{legacy/ui => plugins/kibana_utils}/public/state_management/utils/diff_object.test.ts (100%) rename src/{legacy/ui => plugins/kibana_utils}/public/state_management/utils/diff_object.ts (100%) diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts index 633b7e630700d2..ad5c91d2e19de1 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { Subscription } from 'rxjs'; import { State } from 'ui/state_management/state'; import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; @@ -28,7 +29,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../../plugins/data/public/query/filter_manager/lib/compare_filters'; -type GetAppStateFunc = () => State | undefined | null; +type GetAppStateFunc = () => { filters?: esFilters.Filter[]; save?: () => void } | undefined | null; /** * FilterStateManager is responsible for watching for filter changes @@ -36,10 +37,12 @@ type GetAppStateFunc = () => State | undefined | null; * back to the URL. **/ export class FilterStateManager { + private filterManagerUpdatesSubscription: Subscription; + filterManager: FilterManager; globalState: State; getAppState: GetAppStateFunc; - interval: NodeJS.Timeout | undefined; + interval: number | undefined; constructor(globalState: State, getAppState: GetAppStateFunc, filterManager: FilterManager) { this.getAppState = getAppState; @@ -48,7 +51,7 @@ export class FilterStateManager { this.watchFilterState(); - this.filterManager.getUpdates$().subscribe(() => { + this.filterManagerUpdatesSubscription = this.filterManager.getUpdates$().subscribe(() => { this.updateAppState(); }); } @@ -57,12 +60,13 @@ export class FilterStateManager { if (this.interval) { clearInterval(this.interval); } + this.filterManagerUpdatesSubscription.unsubscribe(); } private watchFilterState() { // This is a temporary solution to remove rootscope. // Moving forward, state should provide observable subscriptions. - this.interval = setInterval(() => { + this.interval = window.setInterval(() => { const appState = this.getAppState(); const stateUndefined = !appState || !this.globalState; if (stateUndefined) return; @@ -95,7 +99,7 @@ export class FilterStateManager { private saveState() { const appState = this.getAppState(); - if (appState) appState.save(); + if (appState && appState.save) appState.save(); this.globalState.save(); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts index ab8dfe81163e48..2b992f95695f38 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts @@ -17,6 +17,5 @@ * under the License. */ -export { getAppStateMock } from './get_app_state_mock'; export { getSavedDashboardMock } from './get_saved_dashboard_mock'; export { getEmbeddableFactoryMock } from './get_embeddable_factories_mock'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b44d1993db23a4..244a58e8a65e5f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -28,8 +28,6 @@ import chrome from 'ui/chrome'; export const legacyChrome = chrome; export { State } from 'ui/state_management/state'; -export { AppState } from 'ui/state_management/app_state'; -export { AppStateClass } from 'ui/state_management/app_state'; export { SavedObjectSaveOpts } from 'ui/saved_objects/types'; export { npSetup, npStart } from 'ui/new_platform'; export { IPrivate } from 'ui/private'; @@ -45,8 +43,6 @@ export { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore -export { AppStateProvider } from 'ui/state_management/app_state'; -// @ts-ignore export { PrivateProvider } from 'ui/private/private'; // @ts-ignore export { EventsProvider } from 'ui/events'; @@ -60,9 +56,7 @@ export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index'; // @ts-ignore export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; -export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectLoader } from 'ui/saved_objects'; export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 7f7bf7cf47bdaf..429a7f7279996f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -31,7 +31,6 @@ import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { GlobalStateProvider, StateManagementConfigProvider, - AppStateProvider, PrivateProvider, EventsProvider, PersistedState, @@ -155,12 +154,6 @@ function createLocalStateModule() { 'app/dashboard/Promise', 'app/dashboard/PersistedState', ]) - .factory('AppState', function(Private: any) { - return Private(AppStateProvider); - }) - .service('getAppState', function(Private: any) { - return Private(AppStateProvider).getAppState; - }) .service('globalState', function(Private: any) { return Private(GlobalStateProvider); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index e9fdc335ba5729..f56990ae82e560 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -20,12 +20,7 @@ import moment from 'moment'; import { Subscription } from 'rxjs'; -import { - AppStateClass as TAppStateClass, - AppState as TAppState, - IInjector, - KbnUrl, -} from '../legacy_imports'; +import { IInjector } from '../legacy_imports'; import { ViewMode } from '../../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; @@ -43,7 +38,7 @@ import { RenderDeps } from './application'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; - appState: TAppState; + appState: DashboardAppState; screenTitle: string; model: { query: Query; @@ -60,7 +55,6 @@ export interface DashboardAppScope extends ng.IScope { refreshInterval: any; panels: SavedDashboardPanel[]; indexPatterns: IIndexPattern[]; - $evalAsync: any; dashboardViewMode: ViewMode; expandedPanel?: string; getShouldShowEditHelp: () => boolean; @@ -91,8 +85,6 @@ export interface DashboardAppScope extends ng.IScope { export function initDashboardAppDirective(app: any, deps: RenderDeps) { app.directive('dashboardApp', function($injector: IInjector) { - const AppState = $injector.get>('AppState'); - const kbnUrl = $injector.get('kbnUrl'); const confirmModal = $injector.get('confirmModal'); const config = deps.uiSettings; @@ -105,17 +97,13 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $routeParams: { id?: string; }, - getAppState: any, globalState: any ) => new DashboardAppController({ $route, $scope, $routeParams, - getAppState, globalState, - kbnUrl, - AppStateClass: AppState, config, confirmModal, indexPatterns: deps.npDataStart.indexPatterns, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 2706b588a2ec4f..7da32ac97126f4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -17,57 +17,55 @@ * under the License. */ -import _ from 'lodash'; +import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; import { Subscription } from 'rxjs'; +import { createHashHistory } from 'history'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { - subscribeWithScope, ConfirmationButtonTypes, - showSaveModal, - SaveResult, migrateLegacyQuery, - State, - AppStateClass as TAppStateClass, - KbnUrl, SavedObjectSaveOpts, - unhashUrl, + SaveResult, + showSaveModal, + State, + subscribeWithScope, } from '../legacy_imports'; import { FilterStateManager } from '../../../../data/public'; import { + esFilters, IndexPattern, + IndexPatternsContract, Query, SavedQuery, - IndexPatternsContract, } from '../../../../../../plugins/data/public'; import { - DashboardContainer, DASHBOARD_CONTAINER_TYPE, + DashboardContainer, DashboardContainerFactory, DashboardContainerInput, DashboardPanelState, } from '../../../../dashboard_embeddable_container/public/np_ready/public'; import { - isErrorEmbeddable, + EmbeddableFactoryNotFoundError, ErrorEmbeddable, - ViewMode, + isErrorEmbeddable, openAddPanelFlyout, - EmbeddableFactoryNotFoundError, + ViewMode, } from '../../../../embeddable_api/public/np_ready/public'; -import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; +import { ConfirmModalFn, NavAction, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; import { showCloneModal } from './top_nav/show_clone_modal'; import { saveDashboard } from './lib'; import { DashboardStateManager } from './dashboard_state_manager'; -import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; +import { createDashboardEditUrl, DashboardConstants } from './dashboard_constants'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { TopNavIds } from './top_nav/top_nav_ids'; import { getDashboardTitle } from './dashboard_strings'; @@ -78,17 +76,15 @@ import { SavedObjectFinderProps, SavedObjectFinderUi, } from '../../../../../../plugins/kibana_react/public'; +import { removeQueryParam, unhashUrl } from '../../../../../../plugins/kibana_utils/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; $route: any; $routeParams: any; - getAppState: any; globalState: State; indexPatterns: IndexPatternsContract; dashboardConfig: any; - kbnUrl: KbnUrl; - AppStateClass: TAppStateClass; config: any; confirmModal: ConfirmModalFn; } @@ -103,12 +99,9 @@ export class DashboardAppController { $scope, $route, $routeParams, - getAppState, globalState, dashboardConfig, localStorage, - kbnUrl, - AppStateClass, indexPatterns, config, confirmModal, @@ -124,7 +117,6 @@ export class DashboardAppController { }, core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http }, }: DashboardAppControllerDependencies) { - new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; let lastReloadRequestTime = 0; @@ -134,14 +126,30 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } + const history = createHashHistory(); const dashboardStateManager = new DashboardStateManager({ savedDashboard: dash, - AppStateClass, + useHashedUrl: config.get('state:storeInSessionStorage'), hideWriteControls: dashboardConfig.getHideWriteControls(), kibanaVersion: injectedMetadata.getKibanaVersion(), + history, }); - $scope.appState = dashboardStateManager.getAppState(); + const filterStateManager = new FilterStateManager( + globalState, + () => { + // Temporary AppState replacement + return { + set filters(_filters: esFilters.Filter[]) { + dashboardStateManager.setFilters(_filters); + }, + get filters() { + return dashboardStateManager.appState.filters; + }, + }; + }, + filterManager + ); // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. @@ -316,8 +324,8 @@ export class DashboardAppController { dirty = true; } + dashboardStateManager.handleDashboardContainerChanges(container); $scope.$evalAsync(() => { - dashboardStateManager.handleDashboardContainerChanges(container); if (dirty) { updateState(); } @@ -337,8 +345,8 @@ export class DashboardAppController { const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]; const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID]; container.addSavedObjectEmbeddable(type, id); - kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_TYPE); - kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_ID); + removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE); + removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID); } } @@ -409,7 +417,9 @@ export class DashboardAppController { key ]; if (!_.isEqual(containerValue, appStateValue)) { - (differences as { [key: string]: unknown })[key] = appStateValue; + // cloneDeep hack is needed, as there are multiple place, where container's input mutated, + // but values from appStateValue are deeply frozen, as they can't be mutated directly + (differences as { [key: string]: unknown })[key] = _.cloneDeep(appStateValue); } }); @@ -560,11 +570,6 @@ export class DashboardAppController { ); function updateViewMode(newMode: ViewMode) { - $scope.topNavMenu = getTopNavConfig( - newMode, - navActions, - dashboardConfig.getHideWriteControls() - ); // eslint-disable-line no-use-before-define dashboardStateManager.switchViewMode(newMode); } @@ -580,17 +585,28 @@ export class DashboardAppController { function revertChangesAndExitEditMode() { dashboardStateManager.resetState(); - kbnUrl.change( - dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL - ); // This is only necessary for new dashboards, which will default to Edit mode. updateViewMode(ViewMode.VIEW); + // Angular's $location skips this update because of history updates from syncState which happen simultaneously + // when calling kbnUrl.change() angular schedules url update and when angular finally starts to process it, + // the update is considered outdated and angular skips it + // so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues + dashboardStateManager.changeDashboardUrl( + dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL + ); + // We need to do a hard reset of the timepicker. appState will not reload like // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on // reload will cause it not to sync. if (dashboardStateManager.getIsTimeSavedWithDashboard()) { - dashboardStateManager.syncTimefilterWithDashboard(timefilter); + // have to use $evalAsync here until '_g' is migrated from $location to state sync utility ('history') + // When state sync utility changes url, angular's $location is missing it's own updates which happen during the same digest cycle + // temporary solution is to delay $location updates to next digest cycle + // unfortunately, these causes 2 browser history entries, but this is temporary and will be fixed after migrating '_g' to state_sync utilities + $scope.$evalAsync(() => { + dashboardStateManager.syncTimefilterWithDashboard(timefilter); + }); } } @@ -642,7 +658,11 @@ export class DashboardAppController { }); if (dash.id !== $routeParams.id) { - kbnUrl.change(createDashboardEditUrl(dash.id)); + // Angular's $location skips this update because of history updates from syncState which happen simultaneously + // when calling kbnUrl.change() angular schedules url update and when angular finally starts to process it, + // the update is considered outdated and angular skips it + // so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues + dashboardStateManager.changeDashboardUrl(createDashboardEditUrl(dash.id)); } else { chrome.docTitle.change(dash.lastSavedTitle); updateViewMode(ViewMode.VIEW); @@ -844,6 +864,15 @@ export class DashboardAppController { }); }); + dashboardStateManager.registerChangeListener(() => { + // view mode could have changed, so trigger top nav update + $scope.topNavMenu = getTopNavConfig( + dashboardStateManager.getViewMode(), + navActions, + dashboardConfig.getHideWriteControls() + ); + }); + $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); visibleSubscription.unsubscribe(); @@ -859,6 +888,9 @@ export class DashboardAppController { if (dashboardContainer) { dashboardContainer.destroy(); } + if (filterStateManager) { + filterStateManager.destroy(); + } }); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts index d9a93b2ceedc31..8806684aab13cf 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts @@ -18,16 +18,12 @@ */ import './np_core.test.mocks'; +import { createBrowserHistory } from 'history'; import { DashboardStateManager } from './dashboard_state_manager'; -import { getAppStateMock, getSavedDashboardMock } from '../__tests__'; -import { AppStateClass } from '../legacy_imports'; -import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; +import { getSavedDashboardMock } from '../__tests__'; +import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; -jest.mock('ui/state_management/state', () => ({ - State: {}, -})); jest.mock('ui/agg_types', () => ({ aggTypes: { metrics: [], @@ -52,9 +48,10 @@ describe('DashboardState', function() { function initDashboardState() { dashboardState = new DashboardStateManager({ savedDashboard, - AppStateClass: getAppStateMock() as AppStateClass, + useHashedUrl: false, hideWriteControls: false, kibanaVersion: '7.0.0', + history: createBrowserHistory(), }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts index 6df18757da6f51..451e7c8ff96db0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts @@ -19,20 +19,16 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; - +import { History } from 'history'; +import { Subscription } from 'rxjs'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../plugins/embeddable/public'; +import { migrateLegacyQuery } from '../legacy_imports'; import { - stateMonitorFactory, - StateMonitor, - AppStateClass as TAppStateClass, - migrateLegacyQuery, -} from '../legacy_imports'; -import { - Query, esFilters, + Query, TimefilterContract as Timefilter, } from '../../../../../../plugins/data/public'; @@ -41,7 +37,20 @@ import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_o import { FilterUtils } from './lib/filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; -import { SavedDashboardPanel, DashboardAppState, DashboardAppStateDefaults } from './types'; +import { + DashboardAppState, + DashboardAppStateDefaults, + DashboardAppStateTransitions, + SavedDashboardPanel, +} from './types'; +import { + createKbnUrlStateStorage, + createStateContainer, + IKbnUrlStateStorage, + ISyncStateRef, + ReduxLikeStateContainer, + syncState, +} from '../../../../../../plugins/kibana_utils/public'; /** * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the @@ -51,7 +60,6 @@ import { SavedDashboardPanel, DashboardAppState, DashboardAppStateDefaults } fro */ export class DashboardStateManager { public savedDashboard: SavedObjectDashboard; - public appState: DashboardAppState; public lastSavedDashboardFilters: { timeTo?: string | Moment; timeFrom?: string | Moment; @@ -63,38 +71,78 @@ export class DashboardStateManager { private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; - private stateMonitor: StateMonitor; + + public get appState(): DashboardAppState { + return this.stateContainer.get(); + } + + private readonly stateContainer: ReduxLikeStateContainer< + DashboardAppState, + DashboardAppStateTransitions + >; + private readonly stateContainerChangeSub: Subscription; + private readonly STATE_STORAGE_KEY = '_a'; + private readonly kbnUrlStateStorage: IKbnUrlStateStorage; + private readonly stateSyncRef: ISyncStateRef; + private readonly history: History; /** * * @param savedDashboard - * @param AppState The AppState class to use when instantiating a new AppState instance. * @param hideWriteControls true if write controls should be hidden. + * @param kibanaVersion current kibanaVersion + * @param */ constructor({ savedDashboard, - AppStateClass, hideWriteControls, kibanaVersion, + useHashedUrl, + history, }: { savedDashboard: SavedObjectDashboard; - AppStateClass: TAppStateClass; hideWriteControls: boolean; kibanaVersion: string; + useHashedUrl: boolean; + history: History; }) { + this.history = history; this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; - this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls); + // get state defaults from saved dashboard, make sure it is migrated + this.stateDefaults = migrateAppState( + getAppStateDefaults(this.savedDashboard, this.hideWriteControls), + kibanaVersion + ); + + this.kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: useHashedUrl, history }); - this.appState = new AppStateClass(this.stateDefaults); + // setup initial state by merging defaults with state from url + // also run migration, as state in url could be of older version + const initialState = migrateAppState( + { + ...this.stateDefaults, + ...this.kbnUrlStateStorage.get(this.STATE_STORAGE_KEY), + }, + kibanaVersion + ); - // Initializing appState does two things - first it translates the defaults into AppState, second it updates - // appState based on the URL (the url trumps the defaults). This means if we update the state format at all and - // want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the - // url. - migrateAppState(this.appState, kibanaVersion); + // setup state container using initial state both from defaults and from url + this.stateContainer = createStateContainer( + initialState, + { + set: state => (prop, value) => ({ ...state, [prop]: value }), + setOption: state => (option, value) => ({ + ...state, + options: { + ...state.options, + [option]: value, + }, + }), + } + ); this.isDirty = false; @@ -104,29 +152,35 @@ export class DashboardStateManager { // in the 'lose changes' warning message. this.lastSavedDashboardFilters = this.getFilterState(); - /** - * Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState. - */ - this.stateMonitor = stateMonitorFactory.create( - this.appState, - this.stateDefaults - ); - - this.stateMonitor.ignoreProps('viewMode'); - // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. - this.stateMonitor.ignoreProps('filters'); - // Query needs to be compared manually because saved legacy queries get migrated in app state automatically - this.stateMonitor.ignoreProps('query'); + this.changeListeners = []; - this.stateMonitor.onChange((status: { dirty: boolean }) => { - this.isDirty = status.dirty; + this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => { + this.isDirty = this.checkIsDirty(); + this.changeListeners.forEach(listener => listener({ dirty: this.isDirty })); }); - this.changeListeners = []; - - this.stateMonitor.onChange((status: { dirty: boolean }) => { - this.changeListeners.forEach(listener => listener(status)); + // make sure url ('_a') matches initial state + this.kbnUrlStateStorage.set(this.STATE_STORAGE_KEY, initialState, { replace: true }); + + // setup state syncing utils. state container will be synched with url into `this.STATE_STORAGE_KEY` query param + this.stateSyncRef = syncState({ + storageKey: this.STATE_STORAGE_KEY, + stateContainer: { + ...this.stateContainer, + set: (state: DashboardAppState | null) => { + // sync state required state container to be able to handle null + // overriding set() so it could handle null coming from url + this.stateContainer.set({ + ...this.stateDefaults, + ...state, + }); + }, + }, + stateStorage: this.kbnUrlStateStorage, }); + + // actually start syncing state with container + this.stateSyncRef.start(); } public registerChangeListener(callback: (status: { dirty: boolean }) => void) { @@ -172,7 +226,7 @@ export class DashboardStateManager { }); if (dirty) { - this.appState.panels = Object.values(convertedPanelStateMap); + this.stateContainer.transitions.set('panels', Object.values(convertedPanelStateMap)); } if (input.isFullScreenMode !== this.getFullScreenMode()) { @@ -184,7 +238,6 @@ export class DashboardStateManager { } this.changeListeners.forEach(listener => listener({ dirty })); - this.saveState(); } public getFullScreenMode() { @@ -192,8 +245,11 @@ export class DashboardStateManager { } public setFullScreenMode(fullScreenMode: boolean) { - this.appState.fullScreenMode = fullScreenMode; - this.saveState(); + this.stateContainer.transitions.set('fullScreenMode', fullScreenMode); + } + + public setFilters(filters: esFilters.Filter[]) { + this.stateContainer.transitions.set('filters', filters); } /** @@ -210,7 +266,10 @@ export class DashboardStateManager { // The right way to fix this might be to ensure the defaults object stored on state is a deep // clone, but given how much code uses the state object, I determined that to be too risky of a change for // now. TODO: revisit this! - this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls); + this.stateDefaults = migrateAppState( + getAppStateDefaults(this.savedDashboard, this.hideWriteControls), + this.kibanaVersion + ); // The original query won't be restored by the above because the query on this.savedDashboard is applied // in place in order for it to affect the visualizations. this.stateDefaults.query = this.lastSavedDashboardFilters.query; @@ -218,9 +277,7 @@ export class DashboardStateManager { this.stateDefaults.filters = [...this.getLastSavedFilterBars()]; this.isDirty = false; - this.appState.setDefaults(this.stateDefaults); - this.appState.reset(); - this.stateMonitor.setInitialState(this.appState.toJSON()); + this.stateContainer.set(this.stateDefaults); } /** @@ -252,31 +309,28 @@ export class DashboardStateManager { } public setDescription(description: string) { - this.appState.description = description; - this.saveState(); + this.stateContainer.transitions.set('description', description); } public setTitle(title: string) { - this.appState.title = title; this.savedDashboard.title = title; - this.saveState(); + this.stateContainer.transitions.set('title', title); } public getAppState() { - return this.appState; + return this.stateContainer.get(); } public getQuery(): Query { - return migrateLegacyQuery(this.appState.query); + return migrateLegacyQuery(this.stateContainer.get().query); } public getSavedQueryId() { - return this.appState.savedQuery; + return this.stateContainer.get().savedQuery; } public setSavedQueryId(id?: string) { - this.appState.savedQuery = id; - this.saveState(); + this.stateContainer.transitions.set('savedQuery', id); } public getUseMargins() { @@ -287,8 +341,7 @@ export class DashboardStateManager { } public setUseMargins(useMargins: boolean) { - this.appState.options.useMargins = useMargins; - this.saveState(); + this.stateContainer.transitions.setOption('useMargins', useMargins); } public getHidePanelTitles() { @@ -296,8 +349,7 @@ export class DashboardStateManager { } public setHidePanelTitles(hidePanelTitles: boolean) { - this.appState.options.hidePanelTitles = hidePanelTitles; - this.saveState(); + this.stateContainer.transitions.setOption('hidePanelTitles', hidePanelTitles); } public getTimeRestore() { @@ -305,8 +357,7 @@ export class DashboardStateManager { } public setTimeRestore(timeRestore: boolean) { - this.appState.timeRestore = timeRestore; - this.saveState(); + this.stateContainer.transitions.set('timeRestore', timeRestore); } public getIsTimeSavedWithDashboard() { @@ -397,7 +448,6 @@ export class DashboardStateManager { (panel: SavedDashboardPanel) => panel.panelIndex === panelIndex ); Object.assign(foundPanel, panelAttributes); - this.saveState(); return foundPanel; } @@ -456,15 +506,37 @@ export class DashboardStateManager { } /** - * Saves the current application state to the URL. + * Synchronously writes current state to url + * returned boolean indicates whether the update happened and if history was updated */ - public saveState() { - this.appState.save(); + private saveState({ replace }: { replace: boolean }): boolean { + // schedules setting current state to url + this.kbnUrlStateStorage.set( + this.STATE_STORAGE_KEY, + this.stateContainer.get() + ); + // immediately forces scheduled updates and changes location + return this.kbnUrlStateStorage.flush({ replace }); + } + + // TODO: find nicer solution for this + // this function helps to make just 1 browser history update, when we imperatively changing the dashboard url + // It could be that there is pending *dashboardStateManager* updates, which aren't flushed yet to the url. + // So to prevent 2 browser updates: + // 1. Force flush any pending state updates (syncing state to query) + // 2. If url was updated, then apply path change with replace + public changeDashboardUrl(pathname: string) { + // synchronously persist current state to url with push() + const updated = this.saveState({ replace: false }); + // change pathname + this.history[updated ? 'replace' : 'push']({ + ...this.history.location, + pathname, + }); } public setQuery(query: Query) { - this.appState.query = query; - this.saveState(); + this.stateContainer.transitions.set('query', query); } /** @@ -472,24 +544,33 @@ export class DashboardStateManager { * @param filter An array of filter bar filters. */ public applyFilters(query: Query, filters: esFilters.Filter[]) { - this.appState.query = query; this.savedDashboard.searchSource.setField('query', query); this.savedDashboard.searchSource.setField('filter', filters); - this.saveState(); + this.stateContainer.transitions.set('query', query); } public switchViewMode(newMode: ViewMode) { - this.appState.viewMode = newMode; - this.saveState(); + this.stateContainer.transitions.set('viewMode', newMode); } /** * Destroys and cleans up this object when it's no longer used. */ public destroy() { - if (this.stateMonitor) { - this.stateMonitor.destroy(); - } + this.stateContainerChangeSub.unsubscribe(); this.savedDashboard.destroy(); + if (this.stateSyncRef) { + this.stateSyncRef.stop(); + } + } + + private checkIsDirty() { + // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. + // Query needs to be compared manually because saved legacy queries get migrated in app state automatically + const propsToIgnore: Array = ['viewMode', 'filters', 'query']; + + const initial = _.omit(this.stateDefaults, propsToIgnore); + const current = _.omit(this.stateContainer.get(), propsToIgnore); + return !_.isEqual(initial, current); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js index 540bfcf5aa6847..7dc408ea4b8013 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js @@ -35,6 +35,7 @@ import { import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { syncOnMount } from './global_state_sync'; +import { createHashHistory } from 'history'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -190,7 +191,7 @@ export function initDashboardApp(app, deps) { template: dashboardTemplate, controller: createNewDashboardCtrl, resolve: { - dash: function($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { + dash: function($rootScope, $route, redirectWhenMissing, kbnUrl) { const id = $route.current.params.id; return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) @@ -216,8 +217,13 @@ export function initDashboardApp(app, deps) { // Preserve BWC of v5.3.0 links for new, unsaved dashboards. // See https://github.com/elastic/kibana/issues/10951 for more context. if (error instanceof SavedObjectNotFound && id === 'create') { - // Note "new AppState" is necessary so the state in the url is preserved through the redirect. - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); + // Note preserve querystring part is necessary so the state is preserved through the redirect. + const history = createHashHistory(); + history.replace({ + ...history.location, // preserve query, + pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, + }); + deps.toastNotifications.addWarning( i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { defaultMessage: diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts index 4aa2461bb65930..73336ec951894e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts @@ -23,7 +23,6 @@ import { SavedDashboardPanel } from '../types'; import { migrateAppState } from './migrate_app_state'; test('migrate app state from 6.0', async () => { - const mockSave = jest.fn(); const appState = { uiState: { 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, @@ -39,11 +38,8 @@ test('migrate app state from 6.0', async () => { type: 'visualization', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -54,12 +50,10 @@ test('migrate app state from 6.0', async () => { expect(newPanel.gridData.y).toBe(0); expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); - expect(mockSave).toBeCalledTimes(1); }); test('migrate sort from 6.1', async () => { const TARGET_VERSION = '8.0'; - const mockSave = jest.fn(); const appState = { uiState: { 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, @@ -76,12 +70,9 @@ test('migrate sort from 6.1', async () => { sort: 'sort', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: false, }; - migrateAppState(appState, TARGET_VERSION); + migrateAppState(appState as any, TARGET_VERSION); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -91,11 +82,9 @@ test('migrate sort from 6.1', async () => { expect((newPanel.embeddableConfig as any).sort).toBe('sort'); expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); - expect(mockSave).toBeCalledTimes(1); }); test('migrates 6.0 even when uiState does not exist', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -109,11 +98,8 @@ test('migrates 6.0 even when uiState does not exist', async () => { sort: 'sort', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -122,11 +108,9 @@ test('migrates 6.0 even when uiState does not exist', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); test('6.2 migration adjusts w & h without margins', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -143,12 +127,9 @@ test('6.2 migration adjusts w & h without margins', async () => { version: '6.2.0', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: false, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -159,11 +140,9 @@ test('6.2 migration adjusts w & h without margins', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); test('6.2 migration adjusts w & h with margins', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -180,12 +159,9 @@ test('6.2 migration adjusts w & h with margins', async () => { version: '6.2.0', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: true, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -196,5 +172,4 @@ test('6.2 migration adjusts w & h with margins', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts index 4083900c7ede7e..0cd958ced0eb1c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts @@ -28,6 +28,7 @@ import { SavedDashboardPanel630, SavedDashboardPanel640To720, SavedDashboardPanel620, + SavedDashboardPanel, } from '../types'; import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; @@ -37,9 +38,9 @@ import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. */ export function migrateAppState( - appState: { [key: string]: unknown } | DashboardAppState, + appState: { [key: string]: unknown } & DashboardAppState, kibanaVersion: string -) { +): DashboardAppState { if (!appState.panels) { throw new Error( i18n.translate('kbn.dashboard.panel.invalidData', { @@ -76,11 +77,11 @@ export function migrateAppState( | SavedDashboardPanel640To720 >, kibanaVersion, - appState.useMargins, - appState.uiState - ); + appState.useMargins as boolean, + appState.uiState as Record> + ) as SavedDashboardPanel[]; delete appState.uiState; - - appState.save(); } + + return appState; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts index 691c87122564f6..d80208ce27ffe4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts @@ -36,16 +36,19 @@ export function saveDashboard( dashboardStateManager: DashboardStateManager, saveOptions: SavedObjectSaveOpts ): Promise { - dashboardStateManager.saveState(); - const savedDashboard = dashboardStateManager.savedDashboard; const appState = dashboardStateManager.appState; updateSavedDashboard(savedDashboard, appState, timeFilter, toJson); return savedDashboard.save(saveOptions).then((id: string) => { - dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState(); - dashboardStateManager.resetState(); + if (id) { + // reset state only when save() was successful + // e.g. save() could be interrupted if title is duplicated and not confirmed + dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState(); + dashboardStateManager.resetState(); + } + return id; }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts index 2072b5d4f6eb06..ec8073c0f72f74 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts @@ -19,13 +19,13 @@ import _ from 'lodash'; import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; -import { AppState } from '../../legacy_imports'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard'; +import { DashboardAppState } from '../types'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, - appState: AppState, + appState: DashboardAppState, timeFilter: TimefilterContract, toJson: (object: T) => string ) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts index e3eb25a208856f..3151fbf821b9fe 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts @@ -18,7 +18,6 @@ */ import { ViewMode } from 'src/plugins/embeddable/public'; -import { AppState } from '../legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -93,11 +92,7 @@ export type SavedDashboardPanelTo60 = Pick< readonly type: string; }; -export type DashboardAppStateDefaults = DashboardAppStateParameters & { - description?: string; -}; - -export interface DashboardAppStateParameters { +export interface DashboardAppState { panels: SavedDashboardPanel[]; fullScreenMode: boolean; title: string; @@ -113,9 +108,24 @@ export interface DashboardAppStateParameters { savedQuery?: string; } -// This could probably be improved if we flesh out AppState more... though AppState will be going away -// so maybe not worth too much time atm. -export type DashboardAppState = DashboardAppStateParameters & AppState; +export type DashboardAppStateDefaults = DashboardAppState & { + description?: string; +}; + +export interface DashboardAppStateTransitions { + set: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState[T] + ) => DashboardAppState; + setOption: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState['options'][T] + ) => DashboardAppState; +} export interface SavedDashboardPanelMap { [key: string]: SavedDashboardPanel; @@ -139,18 +149,3 @@ export type ConfirmModalFn = ( title: string; } ) => void; - -export type AddFilterFn = ( - { - field, - value, - operator, - index, - }: { - field: string; - value: string; - operator: string; - index: string; - }, - appState: AppState -) => void; diff --git a/src/legacy/ui/public/chrome/api/controls.ts b/src/legacy/ui/public/chrome/api/controls.ts index 6dde26be20df26..a95d53ec2eab69 100644 --- a/src/legacy/ui/public/chrome/api/controls.ts +++ b/src/legacy/ui/public/chrome/api/controls.ts @@ -42,4 +42,6 @@ export function initChromeControlsApi(chrome: { [key: string]: any }) { * might be incorrect in the moments just before the UI is updated. */ chrome.getVisible = () => visible$.getValue(); + + chrome.visible$ = visible$.asObservable(); } diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js index 72d26a37a60a16..4c5d7981e962ac 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -30,6 +30,7 @@ import { chromeHeaderNavControlsRegistry, NavControlSide, } from '../../registry/chrome_header_nav_controls'; +import { subscribeWithScope } from '../../utils/subscribe_with_scope'; export function kbnChromeProvider(chrome, internals) { uiModules.get('kibana').directive('kbnChrome', () => { @@ -83,6 +84,15 @@ export function kbnChromeProvider(chrome, internals) { ); } + const chromeVisibility = subscribeWithScope($scope, chrome.visible$, { + next: () => { + // just makes sure change detection is triggered when chrome visibility changes + }, + }); + $scope.$on('$destroy', () => { + chromeVisibility.unsubscribe(); + }); + return chrome; }, }; diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 289d4b8006cba9..c2274eae59f507 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -29,7 +29,6 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import angular from 'angular'; import rison from 'rison-node'; -import { applyDiff } from './utils/diff_object'; import { EventsProvider } from '../events'; import { fatalError, toastNotifications } from '../notify'; import './config_provider'; @@ -38,6 +37,7 @@ import { hashedItemStore, isStateHash, createStateHash, + applyDiff, } from '../../../../plugins/kibana_utils/public'; export function StateProvider( diff --git a/src/plugins/kibana_utils/public/history/index.ts b/src/plugins/kibana_utils/public/history/index.ts new file mode 100644 index 00000000000000..b4b5658c1c886e --- /dev/null +++ b/src/plugins/kibana_utils/public/history/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { removeQueryParam } from './remove_query_param'; diff --git a/src/plugins/kibana_utils/public/history/remove_query_param.test.ts b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts new file mode 100644 index 00000000000000..0b2547ae94668a --- /dev/null +++ b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { removeQueryParam } from './remove_query_param'; +import { createMemoryHistory, Location } from 'history'; + +describe('removeQueryParam', () => { + it('should remove query param from url', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: "?_a=(description:'')&_b=3", + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_a'); + + expect(history.location).toEqual( + expect.objectContaining({ + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: '?_b=3', + state: null, + hash: '', + }) + ); + }); + + it('should not fail if nothing to remove', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: "?_a=(description:'')&_b=3", + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_c'); + + expect(history.location).toEqual(expect.objectContaining(startLocation)); + }); + + it('should not fail if no search', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: '', + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_c'); + + expect(history.location).toEqual(expect.objectContaining(startLocation)); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/plugins/kibana_utils/public/history/remove_query_param.ts similarity index 53% rename from src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts rename to src/plugins/kibana_utils/public/history/remove_query_param.ts index d9dea35a8a1c03..fbf985998b4cd1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts +++ b/src/plugins/kibana_utils/public/history/remove_query_param.ts @@ -17,32 +17,23 @@ * under the License. */ -import { AppStateClass } from '../legacy_imports'; +import { History, Location } from 'history'; +import { parse } from 'querystring'; +import { stringifyQueryString } from '../state_management/url/stringify_query_string'; // TODO: extract it to ../url -/** - * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector. - * This could be improved if we extract the appState and state classes externally of their angular providers. - * @return {AppStateMock} - */ -export function getAppStateMock(): AppStateClass { - class AppStateMock { - constructor(defaults: any) { - Object.assign(this, defaults); - } - - on() {} - off() {} - toJSON() { - return ''; - } - save() {} - translateHashToRison(stateHashOrRison: string | string[]) { - return stateHashOrRison; - } - getQueryParamName() { - return ''; - } +export function removeQueryParam(history: History, param: string, replace: boolean = true) { + const oldLocation = history.location; + const search = (oldLocation.search || '').replace(/^\?/, ''); + const query = parse(search); + delete query[param]; + const newSearch = stringifyQueryString(query); + const newLocation: Location = { + ...oldLocation, + search: newSearch, + }; + if (replace) { + history.replace(newLocation); + } else { + history.push(newLocation); } - - return AppStateMock; } diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index fa58a61e51232b..00c1c95028b4dc 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -58,3 +58,5 @@ export { StartSyncStateFnType, StopSyncStateFnType, } from './state_sync'; +export { removeQueryParam } from './history'; +export { applyDiff } from './state_management/utils/diff_object'; diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts index f1c527d3d53097..6e4c505c62ebc8 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -85,6 +85,7 @@ describe('kbn_url_storage', () => { beforeEach(() => { history = createMemoryHistory(); urlControls = createKbnUrlControls(history); + urlControls.update('/', true); }); const getCurrentUrl = () => createPath(history.location); @@ -143,17 +144,6 @@ describe('kbn_url_storage', () => { expect(cb).toHaveBeenCalledTimes(3); }); - it('should flush async url updates', async () => { - const pr1 = urlControls.updateAsync(() => '/1', false); - const pr2 = urlControls.updateAsync(() => '/2', false); - const pr3 = urlControls.updateAsync(() => '/3', false); - expect(getCurrentUrl()).toBe('/'); - urlControls.flush(); - expect(getCurrentUrl()).toBe('/3'); - await Promise.all([pr1, pr2, pr3]); - expect(getCurrentUrl()).toBe('/3'); - }); - it('flush should take priority over regular replace behaviour', async () => { const pr1 = urlControls.updateAsync(() => '/1', true); const pr2 = urlControls.updateAsync(() => '/2', false); @@ -174,6 +164,48 @@ describe('kbn_url_storage', () => { await Promise.all([pr1, pr2, pr3]); expect(getCurrentUrl()).toBe('/'); }); + + it('should retrieve pending url ', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', true); + expect(urlControls.getPendingUrl()).toEqual('/3'); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + + expect(urlControls.getPendingUrl()).toBeUndefined(); + }); + }); + + describe('urlControls - browser history integration', () => { + let history: History; + let urlControls: IKbnUrlControls; + beforeEach(() => { + history = createBrowserHistory(); + urlControls = createKbnUrlControls(history); + urlControls.update('/', true); + }); + + const getCurrentUrl = () => window.location.href; + + it('should flush async url updates', async () => { + const pr1 = urlControls.updateAsync(() => '/1', false); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', false); + expect(getCurrentUrl()).toBe('http://localhost/'); + expect(urlControls.flush()).toBe('http://localhost/3'); + expect(getCurrentUrl()).toBe('http://localhost/3'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('http://localhost/3'); + }); + + it('flush() should return undefined, if no url updates happened', () => { + expect(urlControls.flush()).toBeUndefined(); + urlControls.updateAsync(() => 'http://localhost/1', false); + urlControls.updateAsync(() => 'http://localhost/', false); + expect(urlControls.flush()).toBeUndefined(); + }); }); describe('getRelativeToHistoryPath', () => { diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 03c136ea3d0928..1dd204e7172132 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -107,25 +107,34 @@ export interface IKbnUrlControls { listen: (cb: () => void) => () => void; /** - * Updates url synchronously + * Updates url synchronously, if needed + * skips the update and returns undefined in case when trying to update to current url + * otherwise returns new url + * * @param url - url to update to * @param replace - use replace instead of push */ - update: (url: string, replace: boolean) => string; + update: (url: string, replace: boolean) => string | undefined; /** * Schedules url update to next microtask, * Useful to batch sync changes to url to cause only one browser history update * @param updater - fn which receives current url and should return next url to update to * @param replace - use replace instead of push + * */ - updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise; + updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise; /** - * Synchronously flushes scheduled url updates + * If there is a pending url update - returns url that is scheduled for update + */ + getPendingUrl: () => string | undefined; + + /** + * Synchronously flushes scheduled url updates. Returns new flushed url, if there was an update. Otherwise - undefined. * @param replace - if replace passed in, then uses it instead of push. Otherwise push or replace is picked depending on updateQueue */ - flush: (replace?: boolean) => string; + flush: (replace?: boolean) => string | undefined; /** * Cancels any pending url updates @@ -143,9 +152,9 @@ export const createKbnUrlControls = ( // if any call in a queue asked to push, then we should push let shouldReplace = true; - function updateUrl(newUrl: string, replace = false): string { + function updateUrl(newUrl: string, replace = false): string | undefined { const currentUrl = getCurrentUrl(); - if (newUrl === currentUrl) return currentUrl; // skip update + if (newUrl === currentUrl) return undefined; // skip update const historyPath = getRelativeToHistoryPath(newUrl, history); @@ -166,15 +175,22 @@ export const createKbnUrlControls = ( // runs scheduled url updates function flush(replace = shouldReplace) { - if (updateQueue.length === 0) return getCurrentUrl(); - const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + const nextUrl = getPendingUrl(); - cleanUp(); + if (!nextUrl) return; - const newUrl = updateUrl(resultUrl, replace); + cleanUp(); + const newUrl = updateUrl(nextUrl, replace); return newUrl; } + function getPendingUrl() { + if (updateQueue.length === 0) return undefined; + const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + + return resultUrl; + } + return { listen: (cb: () => void) => history.listen(() => { @@ -199,6 +215,9 @@ export const createKbnUrlControls = ( cancel: () => { cleanUp(); }, + getPendingUrl: () => { + return getPendingUrl(); + }, }; }; diff --git a/src/legacy/ui/public/state_management/utils/diff_object.test.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts similarity index 100% rename from src/legacy/ui/public/state_management/utils/diff_object.test.ts rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts diff --git a/src/legacy/ui/public/state_management/utils/diff_object.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.ts similarity index 100% rename from src/legacy/ui/public/state_management/utils/diff_object.ts rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.ts diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index 08ad1551420d2e..17f41483a0a21e 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -291,6 +291,42 @@ describe('state_sync', () => { stop(); }); + + it("should preserve reference to unchanged state slices if them didn't change", async () => { + const otherUnchangedSlice = { a: 'test' }; + const oldState = { + todos: container.get().todos, + otherUnchangedSlice, + }; + container.set(oldState as any); + + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + ]); + await urlSyncStrategy.set('_s', container.get()); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!f,id:0,text:'Learning%20state%20containers')))"` + ); + start(); + + history.replace( + "/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!t,id:0,text:'Learning%20state%20containers')))" + ); + + const newState = container.get(); + expect(newState.todos).toEqual([ + { id: 0, text: 'Learning state containers', completed: true }, + ]); + + // reference to unchanged slice is preserved + expect((newState as any).otherUnchangedSlice).toBe(otherUnchangedSlice); + + stop(); + }); }); }); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts index 9c1116e5da5318..28d133829e07c4 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts @@ -24,6 +24,7 @@ import { IStateSyncConfig } from './types'; import { IStateStorage } from './state_sync_state_storage'; import { distinctUntilChangedWithInitialValue } from '../../common'; import { BaseState } from '../state_containers'; +import { applyDiff } from '../state_management/utils/diff_object'; /** * Utility for syncing application state wrapped in state container @@ -100,7 +101,18 @@ export function syncState< const updateState = () => { const newState = stateStorage.get(storageKey); const oldState = stateContainer.get(); - if (!defaultComparator(newState, oldState)) { + if (newState) { + // apply only real differences to new state + const mergedState = { ...oldState } as State; + // merges into 'mergedState' all differences from newState, + // but leaves references if they are deeply the same + const diff = applyDiff(mergedState, newState); + + if (diff.keys.length > 0) { + stateContainer.set(mergedState); + } + } else if (oldState !== newState) { + // empty new state case stateContainer.set(newState); } }; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts index 826122176e061e..cc3f1df7c1e00a 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts @@ -46,9 +46,11 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlStateStorage.flush(); + expect(urlStateStorage.flush()).toBe(true); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`); expect(urlStateStorage.get(key)).toEqual(state); + + expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update }); it('should cancel url updates', async () => { @@ -62,6 +64,19 @@ describe('KbnUrlStateStorage', () => { expect(urlStateStorage.get(key)).toEqual(null); }); + it('should cancel url updates if synchronously returned to the same state', async () => { + const state1 = { test: 'test', ok: 1 }; + const state2 = { test: 'test', ok: 2 }; + const key = '_s'; + const pr1 = urlStateStorage.set(key, state1); + await pr1; + const historyLength = history.length; + const pr2 = urlStateStorage.set(key, state2); + const pr3 = urlStateStorage.set(key, state1); + await Promise.all([pr2, pr3]); + expect(history.length).toBe(historyLength); + }); + it('should notify about url changes', async () => { expect(urlStateStorage.change$).toBeDefined(); const key = '_s'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts index 245006349ad55b..082eaa5095ab94 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts @@ -28,7 +28,11 @@ import { } from '../../state_management/url'; export interface IKbnUrlStateStorage extends IStateStorage { - set: (key: string, state: State, opts?: { replace: boolean }) => Promise; + set: ( + key: string, + state: State, + opts?: { replace: boolean } + ) => Promise; get: (key: string) => State | null; change$: (key: string) => Observable; @@ -36,7 +40,8 @@ export interface IKbnUrlStateStorage extends IStateStorage { cancel: () => void; // synchronously runs any pending url updates - flush: (opts?: { replace?: boolean }) => void; + // returned boolean indicates if change occurred + flush: (opts?: { replace?: boolean }) => boolean; } /** @@ -60,7 +65,11 @@ export const createKbnUrlStateStorage = ( replace ); }, - get: key => getStateFromKbnUrl(key), + get: key => { + // if there is a pending url update, then state will be extracted from that pending url, + // otherwise current url will be used to retrieve state from + return getStateFromKbnUrl(key, url.getPendingUrl()); + }, change$: (key: string) => new Observable(observer => { const unlisten = url.listen(() => { @@ -75,7 +84,7 @@ export const createKbnUrlStateStorage = ( share() ), flush: ({ replace = false }: { replace?: boolean } = {}) => { - url.flush(replace); + return !!url.flush(replace); }, cancel() { url.cancel(); diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js index f5485c1db206e7..8b7f6ba6a34dd3 100644 --- a/test/functional/apps/dashboard/dashboard_clone.js +++ b/test/functional/apps/dashboard/dashboard_clone.js @@ -37,7 +37,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.addVisualizations( PageObjects.dashboard.getTestVisualizationNames() ); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); + await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.clickClone(); await PageObjects.dashboard.confirmClone(); From 9d3d3cdc67e692c90d3bc959984114f007156615 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 21 Jan 2020 13:40:39 +0100 Subject: [PATCH 10/59] Migrate Management views to Kibana Platform plugin (#53880) --- .../saved_objects_client.test.ts | 19 - .../saved_objects/saved_objects_client.ts | 6 +- src/plugins/management/public/index.ts | 7 +- .../legacy/plugins/security/common/model.ts | 28 - x-pack/legacy/plugins/security/index.d.ts | 2 +- x-pack/legacy/plugins/security/index.js | 3 - .../security/public/documentation_links.js | 16 - .../plugins/security/public/images/logout.svg | 3 - .../plugins/security/public/images/person.svg | 3 - .../legacy/plugins/security/public/index.scss | 3 + .../security/public/lib/__tests__/util.js | 49 -- .../legacy/plugins/security/public/lib/api.ts | 56 -- .../security/public/lib/api_keys_api.ts | 47 -- .../plugins/security/public/lib/role_utils.ts | 58 -- .../plugins/security/public/lib/roles_api.ts | 25 - .../public/lib/transform_role_for_save.ts | 41 - .../plugins/security/public/lib/util.js | 19 - .../security/public/objects/lib/get_fields.ts | 15 - .../security/public/objects/lib/roles.ts | 21 - .../security/public/register_feature.js | 29 - .../public/services/shield_indices.js | 18 - .../security/public/services/shield_role.js | 30 - .../plugins/security/public/views/_index.scss | 3 - .../public/views/account/account.html | 1 - .../security/public/views/account/account.js | 27 +- .../security/public/views/login/_index.scss | 3 +- .../public/views/login/components/_index.scss | 1 + .../basic_login_form.test.tsx | 2 +- .../basic_login_form/basic_login_form.tsx | 2 +- .../login/components/login_page/_index.scss | 1 + .../login_page/_login_page.scss} | 5 - .../components/login_page/login_page.test.tsx | 2 +- .../components/login_page/login_page.tsx | 2 +- .../security/public/views/login/login.html | 1 - .../security/public/views/login/login.tsx | 8 +- .../views/login}/login_state.ts | 0 .../login/parse_next.test.ts} | 47 +- .../public/{lib => views/login}/parse_next.ts | 0 .../public/views/management/_index.scss | 4 - .../management/api_keys_grid/api_keys.html | 3 - .../management/api_keys_grid/api_keys.js | 35 - .../public/views/management/breadcrumbs.ts | 115 --- .../_change_password_form.scss | 17 - .../change_password_form/_index.scss | 1 - .../change_password_form.html | 141 ---- .../change_password_form.js | 48 -- .../views/management/edit_role/_index.scss | 1 - .../edit_role/components/_index.scss | 9 - .../components/edit_role_page.test.tsx | 716 ------------------ .../edit_role/components/edit_role_page.tsx | 409 ---------- .../es/elasticsearch_privileges.test.tsx | 96 --- .../views/management/edit_role/edit_role.html | 3 - .../views/management/edit_role/index.js | 176 ----- .../views/management/edit_user/_index.scss | 1 - .../views/management/edit_user/edit_user.html | 3 - .../views/management/edit_user/edit_user.js | 58 -- .../public/views/management/management.js | 134 ---- .../password_form/password_form.html | 53 -- .../management/password_form/password_form.js | 24 - .../edit_role_mapping/_index.scss | 1 - .../edit_role_mapping_page.test.tsx | 341 --------- .../edit_role_mapping/edit_role_mapping.html | 3 - .../role_mappings/edit_role_mapping/index.tsx | 45 -- .../role_mappings_grid_page.test.tsx | 182 ----- .../role_mappings_grid/index.tsx | 40 - .../role_mappings_grid/role_mappings.html | 3 - .../views/management/roles_grid/roles.html | 3 - .../views/management/roles_grid/roles.js | 35 - .../views/management/users_grid/users.html | 3 - .../views/management/users_grid/users.js | 47 -- .../overwritten_session.tsx | 3 +- x-pack/plugins/security/common/model/index.ts | 11 +- .../security/common/model/role.test.ts} | 9 +- x-pack/plugins/security/common/model/role.ts | 51 ++ x-pack/plugins/security/kibana.json | 3 +- x-pack/plugins/security/public/_index.scss | 2 + .../account_management_page.test.tsx | 37 +- .../account_management_page.tsx | 18 +- .../change_password/change_password.tsx | 19 +- .../change_password/index.ts | 0 .../public/account_management}/index.ts | 0 .../personal_info/index.ts | 0 .../personal_info/personal_info.tsx | 11 +- .../security/public/management/_index.scss | 3 + .../api_keys/api_keys_api_client.mock.ts | 13 + .../api_keys/api_keys_api_client.test.ts | 86 +++ .../api_keys/api_keys_api_client.ts | 42 + .../api_keys_grid_page.test.tsx.snap | 20 +- .../api_keys_grid_page.test.tsx | 124 +-- .../api_keys_grid}/api_keys_grid_page.tsx | 50 +- .../empty_prompt/empty_prompt.tsx | 7 +- .../api_keys_grid}/empty_prompt/index.ts | 0 .../api_keys/api_keys_grid}/index.ts | 2 +- .../invalidate_provider/index.ts | 0 .../invalidate_provider.tsx | 21 +- .../api_keys_grid}/not_enabled/index.ts | 0 .../not_enabled/not_enabled.tsx | 10 +- .../api_keys_grid}/permission_denied/index.ts | 0 .../permission_denied/permission_denied.tsx | 0 .../api_keys/api_keys_management_app.test.tsx | 54 ++ .../api_keys/api_keys_management_app.tsx | 58 ++ .../api_keys}/documentation_links.ts | 16 +- .../public/management/api_keys/index.mock.ts | 7 + .../public/management/api_keys/index.ts | 7 + .../security/public/management/index.ts | 8 + .../management/management_service.test.ts | 226 ++++++ .../public/management/management_service.ts | 95 +++ .../public}/management/management_urls.ts | 9 +- .../management/role_mappings/_index.scss | 1 + .../delete_provider/delete_provider.test.tsx | 152 ++-- .../delete_provider/delete_provider.tsx | 21 +- .../components/delete_provider/index.ts | 0 .../role_mappings/components/index.ts | 0 .../components/no_compatible_realms/index.ts | 0 .../no_compatible_realms.tsx | 10 +- .../components/permission_denied/index.ts | 0 .../permission_denied/permission_denied.tsx | 0 .../components/section_loading/index.ts | 0 .../section_loading/section_loading.test.tsx | 0 .../section_loading/section_loading.tsx | 0 .../role_mappings}/documentation_links.ts | 12 +- .../edit_role_mapping/_index.scss | 1 + .../edit_role_mapping_page.test.tsx | 399 ++++++++++ .../edit_role_mapping_page.tsx | 44 +- .../role_mappings/edit_role_mapping}/index.ts | 0 .../mapping_info_panel/index.ts | 0 .../mapping_info_panel.test.tsx | 36 +- .../mapping_info_panel/mapping_info_panel.tsx | 13 +- .../add_role_template_button.test.tsx | 0 .../add_role_template_button.tsx | 0 .../role_selector/index.tsx | 0 .../role_selector/role_selector.test.tsx | 25 +- .../role_selector/role_selector.tsx | 7 +- .../role_template_editor.test.tsx | 0 .../role_selector/role_template_editor.tsx | 4 +- .../role_template_type_select.tsx | 4 +- .../rule_editor_panel/_index.scss | 1 + .../_rule_editor_group.scss} | 0 .../add_rule_button.test.tsx | 2 +- .../rule_editor_panel/add_rule_button.tsx | 2 +- .../field_rule_editor.test.tsx | 2 +- .../rule_editor_panel/field_rule_editor.tsx | 2 +- .../rule_editor_panel/index.tsx | 0 .../json_rule_editor.test.tsx | 10 +- .../rule_editor_panel/json_rule_editor.tsx | 7 +- .../rule_editor_panel.test.tsx | 9 +- .../rule_editor_panel/rule_editor_panel.tsx | 14 +- .../rule_group_editor.test.tsx | 2 +- .../rule_editor_panel/rule_group_editor.tsx | 4 +- .../rule_editor_panel/rule_group_title.tsx | 9 +- .../visual_rule_editor.test.tsx | 2 +- .../rule_editor_panel/visual_rule_editor.tsx | 6 +- .../services/is_rule_group.ts | 0 .../services/role_mapping_constants.ts | 0 .../services/role_mapping_validation.test.ts | 2 +- .../services/role_mapping_validation.ts | 2 +- .../services/role_template_type.test.ts | 2 +- .../services/role_template_type.ts | 2 +- .../management/role_mappings/index.mock.ts | 7 + .../public/management/role_mappings/index.ts | 7 + .../__snapshots__/rule_builder.test.ts.snap | 0 .../role_mappings/model/all_rule.test.ts | 0 .../role_mappings/model/all_rule.ts | 0 .../role_mappings/model/any_rule.test.ts | 0 .../role_mappings/model/any_rule.ts | 0 .../model/except_all_rule.test.ts | 0 .../role_mappings/model/except_all_rule.ts | 0 .../model/except_any_rule.test.ts | 0 .../role_mappings/model/except_any_rule.ts | 0 .../role_mappings/model/field_rule.test.ts | 0 .../role_mappings/model/field_rule.ts | 0 .../management/role_mappings/model/index.ts | 0 .../management/role_mappings/model/rule.ts | 0 .../role_mappings/model/rule_builder.test.ts | 2 +- .../role_mappings/model/rule_builder.ts | 2 +- .../role_mappings/model/rule_builder_error.ts | 0 .../role_mappings/model/rule_group.ts | 2 +- .../role_mappings_api_client.mock.ts | 15 + .../role_mappings_api_client.ts} | 8 +- .../create_role_mapping_button.tsx | 2 +- .../create_role_mapping_button/index.ts | 0 .../empty_prompt/empty_prompt.tsx | 0 .../role_mappings_grid}/empty_prompt/index.ts | 0 .../role_mappings_grid}/index.ts | 0 .../role_mappings_grid_page.test.tsx | 219 ++++++ .../role_mappings_grid_page.tsx | 29 +- .../role_mappings_management_app.test.tsx | 127 ++++ .../role_mappings_management_app.tsx | 104 +++ .../public/management/roles/_index.scss | 1 + .../management/roles/documentation_links.ts | 27 + .../__snapshots__/validate_role.test.ts.snap | 0 .../management/roles/edit_role/_index.scss | 3 + .../collapsible_panel.test.tsx.snap | 0 .../collapsible_panel/_collapsible_panel.scss | 0 .../edit_role/collapsible_panel/_index.scss | 1 + .../collapsible_panel.test.tsx | 0 .../collapsible_panel/collapsible_panel.tsx | 1 - .../edit_role}/collapsible_panel/index.ts | 0 .../edit_role}/delete_role_button.test.tsx | 6 +- .../roles/edit_role}/delete_role_button.tsx | 8 +- .../roles/edit_role/edit_role_page.test.tsx | 552 ++++++++++++++ .../roles/edit_role/edit_role_page.tsx | 594 +++++++++++++++ .../management/roles/edit_role}/index.ts | 0 .../roles/edit_role}/privilege_utils.test.ts | 0 .../roles/edit_role}/privilege_utils.ts | 2 +- .../roles/edit_role/privileges/_index.scss | 2 + .../privileges/_privilege_feature_icon.scss | 4 + .../cluster_privileges.test.tsx.snap | 0 .../elasticsearch_privileges.test.tsx.snap | 24 +- .../index_privilege_form.test.tsx.snap | 0 .../index_privileges.test.tsx.snap | 0 .../privileges/es/cluster_privileges.test.tsx | 2 +- .../privileges/es/cluster_privileges.tsx | 3 +- .../es/elasticsearch_privileges.test.tsx | 67 ++ .../es/elasticsearch_privileges.tsx | 34 +- .../es/index_privilege_form.test.tsx | 2 +- .../privileges/es/index_privilege_form.tsx | 7 +- .../privileges/es/index_privileges.test.tsx | 27 +- .../privileges/es/index_privileges.tsx | 32 +- .../roles/edit_role}/privileges/index.ts | 0 .../kibana_privileges_region.test.tsx.snap | 0 .../edit_role/privileges/kibana/_index.scss | 2 + .../edit_role/privileges/kibana}/constants.ts | 0 .../__snapshots__/feature_table.test.tsx.snap | 0 .../_change_all_privileges.scss} | 0 .../kibana/feature_table/_index.scss | 1 + .../feature_table/change_all_privileges.tsx | 0 .../feature_table/feature_table.test.tsx | 7 +- .../kibana/feature_table/feature_table.tsx | 32 +- .../privileges/kibana/feature_table/index.ts | 0 .../__fixtures__/build_role.ts | 2 +- .../__fixtures__/common_allowed_privileges.ts | 0 .../default_privilege_definition.ts | 2 +- .../__fixtures__/index.ts | 0 .../kibana_privilege_calculator/index.ts | 0 ...bana_allowed_privileges_calculator.test.ts | 2 +- .../kibana_allowed_privileges_calculator.ts | 11 +- .../kibana_base_privilege_calculator.test.ts | 6 +- .../kibana_base_privilege_calculator.ts | 9 +- ...ibana_feature_privilege_calculator.test.ts | 6 +- .../kibana_feature_privilege_calculator.ts | 13 +- .../kibana_privilege_calculator.test.ts | 4 +- .../kibana_privilege_calculator.ts | 5 +- .../kibana_privilege_calculator_types.ts | 0 .../kibana_privileges_calculator_factory.ts | 12 +- .../kibana/kibana_privileges_region.test.tsx | 4 +- .../kibana/kibana_privileges_region.tsx | 17 +- .../simple_privilege_section.test.tsx.snap | 0 .../kibana/simple_privilege_section/index.ts | 0 .../privilege_selector.tsx | 2 +- .../simple_privilege_section.test.tsx | 8 +- .../simple_privilege_section.tsx | 21 +- .../unsupported_space_privileges_warning.tsx | 0 .../__fixtures__/index.ts | 0 .../__fixtures__/raw_kibana_privileges.ts | 2 +- .../privilege_display.test.tsx.snap | 0 .../privilege_space_form.test.tsx.snap | 102 --- ...pace_aware_privilege_section.test.tsx.snap | 0 .../space_aware_privilege_section/_index.scss | 0 .../_privilege_matrix.scss | 2 +- .../space_aware_privilege_section/index.ts | 0 .../privilege_display.test.tsx | 2 +- .../privilege_display.tsx | 7 +- .../privilege_matrix.test.tsx | 10 +- .../privilege_matrix.tsx | 17 +- .../privilege_space_form.test.tsx | 4 +- .../privilege_space_form.tsx | 14 +- .../privilege_space_table.test.tsx | 4 +- .../privilege_space_table.tsx | 15 +- .../space_aware_privilege_section.test.tsx | 6 +- .../space_aware_privilege_section.tsx | 15 +- .../space_selector.tsx | 4 +- .../kibana/transform_error_section/index.ts | 0 .../transform_error_section.tsx | 0 .../edit_role}/reserved_role_badge.test.tsx | 2 +- .../roles/edit_role}/reserved_role_badge.tsx | 3 +- .../edit_role/spaces_popover_list/_index.scss | 1 + .../_spaces_popover_list.scss | 0 .../edit_role}/spaces_popover_list/index.ts | 0 .../spaces_popover_list.tsx | 7 +- .../roles/edit_role}/validate_role.test.ts | 2 +- .../roles/edit_role}/validate_role.ts | 2 +- .../public/management/roles/index.mock.ts | 9 + .../security/public/management/roles/index.ts | 8 + .../roles/indices_api_client.mock.ts | 11 + .../management/roles/indices_api_client.ts | 18 + .../roles/privileges_api_client.mock.ts | 12 + .../management/roles/privileges_api_client.ts | 22 + .../management/roles/roles_api_client.mock.ts | 14 + .../roles/roles_api_client.test.ts} | 73 +- .../management/roles/roles_api_client.ts | 62 ++ .../roles_grid_page.test.tsx.snap | 0 .../confirm_delete/confirm_delete.tsx | 57 +- .../roles/roles_grid}/confirm_delete/index.ts | 0 .../management/roles/roles_grid}/index.ts | 0 .../roles_grid}/permission_denied/index.ts | 0 .../permission_denied/permission_denied.tsx | 0 .../roles_grid}/roles_grid_page.test.tsx | 89 ++- .../roles/roles_grid}/roles_grid_page.tsx | 89 +-- .../roles/roles_management_app.test.tsx | 160 ++++ .../management/roles/roles_management_app.tsx | 116 +++ .../public/management/users/_index.scss | 1 + .../change_password_form.test.tsx | 24 +- .../change_password_form.tsx | 16 +- .../components}/change_password_form/index.ts | 0 .../confirm_delete_users.test.tsx} | 53 +- .../confirm_delete_users.tsx} | 81 +- .../components/confirm_delete_users}/index.ts | 2 +- .../management/users/components/index.ts | 8 + .../users/edit_user/_edit_user_page.scss} | 0 .../management/users/edit_user/_index.scss | 1 + .../users/edit_user}/edit_user_page.test.tsx | 55 +- .../users/edit_user}/edit_user_page.tsx | 175 ++--- .../management/users/edit_user}/index.ts | 0 .../users/edit_user}/validate_user.test.ts | 2 +- .../users/edit_user}/validate_user.ts | 2 +- .../public/management/users/index.mock.ts} | 2 +- .../public/management/users}/index.ts | 5 +- .../management/users/user_api_client.mock.ts | 15 + .../management/users/user_api_client.ts | 45 ++ .../management/users/users_grid}/index.ts | 2 +- .../users_grid/users_grid_page.test.tsx} | 34 +- .../users/users_grid/users_grid_page.tsx} | 52 +- .../users/users_management_app.test.tsx | 131 ++++ .../management/users/users_management_app.tsx | 94 +++ x-pack/plugins/security/public/plugin.ts | 74 -- x-pack/plugins/security/public/plugin.tsx | 147 ++++ .../server/routes/role_mapping/get.ts | 2 +- x-pack/plugins/spaces/common/index.ts | 2 +- .../translations/translations/ja-JP.json | 15 - .../translations/translations/zh-CN.json | 15 - .../functional/apps/security/management.js | 2 +- .../functional/apps/security/role_mappings.ts | 2 +- 333 files changed, 4797 insertions(+), 4496 deletions(-) delete mode 100644 x-pack/legacy/plugins/security/common/model.ts delete mode 100644 x-pack/legacy/plugins/security/public/documentation_links.js delete mode 100644 x-pack/legacy/plugins/security/public/images/logout.svg delete mode 100644 x-pack/legacy/plugins/security/public/images/person.svg delete mode 100644 x-pack/legacy/plugins/security/public/lib/__tests__/util.js delete mode 100644 x-pack/legacy/plugins/security/public/lib/api.ts delete mode 100644 x-pack/legacy/plugins/security/public/lib/api_keys_api.ts delete mode 100644 x-pack/legacy/plugins/security/public/lib/role_utils.ts delete mode 100644 x-pack/legacy/plugins/security/public/lib/roles_api.ts delete mode 100644 x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts delete mode 100644 x-pack/legacy/plugins/security/public/lib/util.js delete mode 100644 x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts delete mode 100644 x-pack/legacy/plugins/security/public/objects/lib/roles.ts delete mode 100644 x-pack/legacy/plugins/security/public/register_feature.js delete mode 100644 x-pack/legacy/plugins/security/public/services/shield_indices.js delete mode 100644 x-pack/legacy/plugins/security/public/services/shield_role.js delete mode 100644 x-pack/legacy/plugins/security/public/views/account/account.html create mode 100644 x-pack/legacy/plugins/security/public/views/login/components/_index.scss create mode 100644 x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss rename x-pack/legacy/plugins/security/public/views/login/{_login.scss => components/login_page/_login_page.scss} (88%) delete mode 100644 x-pack/legacy/plugins/security/public/views/login/login.html rename x-pack/legacy/plugins/security/{common => public/views/login}/login_state.ts (100%) rename x-pack/legacy/plugins/security/public/{lib/__tests__/parse_next.js => views/login/parse_next.test.ts} (80%) rename x-pack/legacy/plugins/security/public/{lib => views/login}/parse_next.ts (100%) delete mode 100644 x-pack/legacy/plugins/security/public/views/management/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts delete mode 100644 x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_role/index.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/management.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx delete mode 100644 x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js delete mode 100644 x-pack/legacy/plugins/security/public/views/management/users_grid/users.html delete mode 100644 x-pack/legacy/plugins/security/public/views/management/users_grid/users.js rename x-pack/{legacy/plugins/security/public/lib/role_utils.test.ts => plugins/security/common/model/role.test.ts} (96%) create mode 100644 x-pack/plugins/security/public/_index.scss rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/account_management_page.test.tsx (72%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/account_management_page.tsx (62%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/change_password/change_password.tsx (81%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/change_password/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/personal_info/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/account/components => plugins/security/public/account_management}/personal_info/personal_info.tsx (89%) create mode 100644 x-pack/plugins/security/public/management/_index.scss create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/__snapshots__/api_keys_grid_page.test.tsx.snap (91%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/api_keys_grid_page.test.tsx (65%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/api_keys_grid_page.tsx (90%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/empty_prompt/empty_prompt.tsx (89%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/empty_prompt/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/index.ts (81%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/invalidate_provider/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/invalidate_provider/invalidate_provider.tsx (91%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/not_enabled/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/not_enabled/not_enabled.tsx (78%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/permission_denied/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/components => plugins/security/public/management/api_keys/api_keys_grid}/permission_denied/permission_denied.tsx (100%) create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx rename x-pack/{legacy/plugins/security/public/views/management/api_keys_grid/services => plugins/security/public/management/api_keys}/documentation_links.ts (51%) create mode 100644 x-pack/plugins/security/public/management/api_keys/index.mock.ts create mode 100644 x-pack/plugins/security/public/management/api_keys/index.ts create mode 100644 x-pack/plugins/security/public/management/index.ts create mode 100644 x-pack/plugins/security/public/management/management_service.test.ts create mode 100644 x-pack/plugins/security/public/management/management_service.ts rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/management_urls.ts (74%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/_index.scss rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/delete_provider/delete_provider.test.tsx (64%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/delete_provider/delete_provider.tsx (93%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/delete_provider/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/no_compatible_realms/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx (78%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/permission_denied/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/permission_denied/permission_denied.tsx (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/section_loading/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/section_loading/section_loading.test.tsx (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/components/section_loading/section_loading.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/services => plugins/security/public/management/role_mappings}/documentation_links.ts (70%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/_index.scss create mode 100644 x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/edit_role_mapping_page.tsx (87%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/mapping_info_panel/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/mapping_info_panel/mapping_info_panel.test.tsx (82%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/mapping_info_panel/mapping_info_panel.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/add_role_template_button.test.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/add_role_template_button.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/index.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/role_selector.test.tsx (85%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/role_selector.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/role_template_editor.test.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/role_template_editor.tsx (98%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/role_selector/role_template_type_select.tsx (92%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss => plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_rule_editor_group.scss} (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/add_rule_button.test.tsx (97%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/add_rule_button.tsx (97%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/field_rule_editor.test.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/field_rule_editor.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/index.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/json_rule_editor.test.tsx (89%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/json_rule_editor.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/rule_editor_panel.test.tsx (88%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/rule_editor_panel.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/rule_group_editor.test.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/rule_group_editor.tsx (97%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/rule_group_title.tsx (97%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/visual_rule_editor.test.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components => plugins/security/public/management/role_mappings/edit_role_mapping}/rule_editor_panel/visual_rule_editor.tsx (95%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/is_rule_group.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts (98%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts (97%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts (96%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/edit_role_mapping/services/role_template_type.ts (96%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/index.mock.ts create mode 100644 x-pack/plugins/security/public/management/role_mappings/index.ts rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/all_rule.test.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/all_rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/any_rule.test.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/any_rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/except_all_rule.test.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/except_all_rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/except_any_rule.test.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/except_any_rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/field_rule.test.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/field_rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/rule.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/rule_builder.test.ts (99%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/rule_builder.ts (99%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/rule_builder_error.ts (100%) rename x-pack/{legacy/plugins/security/public/views => plugins/security/public}/management/role_mappings/model/rule_group.ts (94%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.mock.ts rename x-pack/{legacy/plugins/security/public/lib/role_mappings_api.ts => plugins/security/public/management/role_mappings/role_mappings_api_client.ts} (89%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/create_role_mapping_button/create_role_mapping_button.tsx (90%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/create_role_mapping_button/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/empty_prompt/empty_prompt.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/empty_prompt/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/index.ts (100%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx rename x-pack/{legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components => plugins/security/public/management/role_mappings/role_mappings_grid}/role_mappings_grid_page.tsx (93%) create mode 100644 x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx create mode 100644 x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx create mode 100644 x-pack/plugins/security/public/management/roles/_index.scss create mode 100644 x-pack/plugins/security/public/management/roles/documentation_links.ts rename x-pack/{legacy/plugins/security/public/views/management/edit_role/lib => plugins/security/public/management/roles/edit_role}/__snapshots__/validate_role.test.ts.snap (100%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/collapsible_panel/__snapshots__/collapsible_panel.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/collapsible_panel/_collapsible_panel.scss (100%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/collapsible_panel/collapsible_panel.test.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/collapsible_panel/collapsible_panel.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/collapsible_panel/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/delete_role_button.test.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/delete_role_button.tsx (95%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/index.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role}/privilege_utils.test.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role}/privilege_utils.ts (93%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/privileges/_index.scss create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/privileges/_privilege_feature_icon.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap (87%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/__snapshots__/index_privileges.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/cluster_privileges.test.tsx (96%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/cluster_privileges.tsx (93%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/elasticsearch_privileges.tsx (89%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/index_privilege_form.test.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/index_privilege_form.tsx (98%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/index_privileges.test.tsx (74%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/es/index_privileges.tsx (84%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap (100%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/constants.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/feature_table/__snapshots__/feature_table.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/_index.scss => plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_change_all_privileges.scss} (100%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/feature_table/change_all_privileges.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/feature_table/feature_table.test.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/feature_table/feature_table.tsx (91%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/feature_table/index.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/__fixtures__/build_role.ts (90%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/__fixtures__/common_allowed_privileges.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/__fixtures__/default_privilege_definition.ts (94%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/__fixtures__/index.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/index.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_allowed_privileges_calculator.test.ts (99%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_allowed_privileges_calculator.ts (94%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_base_privilege_calculator.test.ts (98%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_base_privilege_calculator.ts (91%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_feature_privilege_calculator.test.ts (99%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_feature_privilege_calculator.ts (94%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_privilege_calculator.test.ts (99%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_privilege_calculator.ts (96%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_privilege_calculator_types.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/roles/edit_role/privileges/kibana}/kibana_privilege_calculator/kibana_privileges_calculator_factory.ts (90%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/kibana_privileges_region.test.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/kibana_privileges_region.tsx (79%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/privilege_selector.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/__fixtures__/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts (94%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_display.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap (82%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/_index.scss (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss (87%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx (96%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_display.tsx (96%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx (89%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx (93%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx (96%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx (99%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx (94%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx (93%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/space_aware_privilege_section/space_selector.tsx (93%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/transform_error_section/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/privileges/kibana/transform_error_section/transform_error_section.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/reserved_role_badge.test.tsx (96%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/reserved_role_badge.tsx (89%) create mode 100644 x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/spaces_popover_list/_spaces_popover_list.scss (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/spaces_popover_list/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/components => plugins/security/public/management/roles/edit_role}/spaces_popover_list/spaces_popover_list.tsx (95%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/lib => plugins/security/public/management/roles/edit_role}/validate_role.test.ts (99%) rename x-pack/{legacy/plugins/security/public/views/management/edit_role/lib => plugins/security/public/management/roles/edit_role}/validate_role.ts (98%) create mode 100644 x-pack/plugins/security/public/management/roles/index.mock.ts create mode 100644 x-pack/plugins/security/public/management/roles/index.ts create mode 100644 x-pack/plugins/security/public/management/roles/indices_api_client.mock.ts create mode 100644 x-pack/plugins/security/public/management/roles/indices_api_client.ts create mode 100644 x-pack/plugins/security/public/management/roles/privileges_api_client.mock.ts create mode 100644 x-pack/plugins/security/public/management/roles/privileges_api_client.ts create mode 100644 x-pack/plugins/security/public/management/roles/roles_api_client.mock.ts rename x-pack/{legacy/plugins/security/public/lib/transform_role_for_save.test.ts => plugins/security/public/management/roles/roles_api_client.test.ts} (82%) create mode 100644 x-pack/plugins/security/public/management/roles/roles_api_client.ts rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/__snapshots__/roles_grid_page.test.tsx.snap (100%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/confirm_delete/confirm_delete.tsx (72%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/confirm_delete/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/permission_denied/index.ts (100%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/permission_denied/permission_denied.tsx (100%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/roles_grid_page.test.tsx (63%) rename x-pack/{legacy/plugins/security/public/views/management/roles_grid/components => plugins/security/public/management/roles/roles_grid}/roles_grid_page.tsx (78%) create mode 100644 x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx create mode 100644 x-pack/plugins/security/public/management/roles/roles_management_app.tsx create mode 100644 x-pack/plugins/security/public/management/users/_index.scss rename x-pack/{legacy/plugins/security/public/components/management => plugins/security/public/management/users/components}/change_password_form/change_password_form.test.tsx (82%) rename x-pack/{legacy/plugins/security/public/components/management => plugins/security/public/management/users/components}/change_password_form/change_password_form.tsx (96%) rename x-pack/{legacy/plugins/security/public/components/management => plugins/security/public/management/users/components}/change_password_form/index.ts (100%) rename x-pack/{legacy/plugins/security/public/components/management/users/confirm_delete.test.tsx => plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx} (58%) rename x-pack/{legacy/plugins/security/public/components/management/users/confirm_delete.tsx => plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx} (50%) rename x-pack/{legacy/plugins/security/public/components/management/users => plugins/security/public/management/users/components/confirm_delete_users}/index.ts (79%) create mode 100644 x-pack/plugins/security/public/management/users/components/index.ts rename x-pack/{legacy/plugins/security/public/views/management/edit_user/_users.scss => plugins/security/public/management/users/edit_user/_edit_user_page.scss} (100%) create mode 100644 x-pack/plugins/security/public/management/users/edit_user/_index.scss rename x-pack/{legacy/plugins/security/public/views/management/edit_user/components => plugins/security/public/management/users/edit_user}/edit_user_page.test.tsx (70%) rename x-pack/{legacy/plugins/security/public/views/management/edit_user/components => plugins/security/public/management/users/edit_user}/edit_user_page.tsx (77%) rename x-pack/{legacy/plugins/security/public/views/management/edit_user/components => plugins/security/public/management/users/edit_user}/index.ts (100%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/users/edit_user}/validate_user.test.ts (98%) rename x-pack/{legacy/plugins/security/public/lib => plugins/security/public/management/users/edit_user}/validate_user.ts (98%) rename x-pack/{legacy/plugins/security/public/views/management/index.js => plugins/security/public/management/users/index.mock.ts} (80%) rename x-pack/{legacy/plugins/security/public/objects => plugins/security/public/management/users}/index.ts (68%) create mode 100644 x-pack/plugins/security/public/management/users/user_api_client.mock.ts create mode 100644 x-pack/plugins/security/public/management/users/user_api_client.ts rename x-pack/{legacy/plugins/security/public/views/management/users_grid/components => plugins/security/public/management/users/users_grid}/index.ts (82%) rename x-pack/{legacy/plugins/security/public/views/management/users_grid/components/users_list_page.test.tsx => plugins/security/public/management/users/users_grid/users_grid_page.test.tsx} (63%) rename x-pack/{legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx => plugins/security/public/management/users/users_grid/users_grid_page.tsx} (85%) create mode 100644 x-pack/plugins/security/public/management/users/users_management_app.test.tsx create mode 100644 x-pack/plugins/security/public/management/users/users_management_app.tsx delete mode 100644 x-pack/plugins/security/public/plugin.ts create mode 100644 x-pack/plugins/security/public/plugin.tsx diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index e633e00965c6ab..0c34a16c68e99d 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -448,23 +448,4 @@ describe('SavedObjectsClient', () => { `); }); }); - - it('maintains backwards compatibility by transforming http.fetch errors to be compatible with kfetch errors', () => { - const err = { - response: { ok: false, redirected: false, status: 409, statusText: 'Conflict' }, - body: 'response body', - }; - http.fetch.mockRejectedValue(err); - return expect(savedObjectsClient.get(doc.type, doc.id)).rejects.toMatchInlineSnapshot(` - Object { - "body": "response body", - "res": Object { - "ok": false, - "redirected": false, - "status": 409, - "statusText": "Conflict", - }, - } - `); - }); }); diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index dab98ee66cdb10..ccb23793a85349 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -465,11 +465,7 @@ export class SavedObjectsClient { * uses `{response: {status: number}}`. */ private savedObjectsFetch(path: string, { method, query, body }: HttpFetchOptions) { - return this.http.fetch(path, { method, query, body }).catch(err => { - const kfetchError = Object.assign(err, { res: err.response }); - delete kfetchError.response; - return Promise.reject(kfetchError); - }); + return this.http.fetch(path, { method, query, body }); } } diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index faec466dbd6714..4ece75bbf36da3 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -24,7 +24,12 @@ export function plugin(initializerContext: PluginInitializerContext) { return new ManagementPlugin(); } -export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types'; +export { + ManagementSetup, + ManagementStart, + RegisterManagementApp, + RegisterManagementAppArgs, +} from './types'; export { ManagementApp } from './management_app'; export { ManagementSection } from './management_section'; export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/x-pack/legacy/plugins/security/common/model.ts b/x-pack/legacy/plugins/security/common/model.ts deleted file mode 100644 index 733e89f774db8c..00000000000000 --- a/x-pack/legacy/plugins/security/common/model.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - ApiKey, - ApiKeyToInvalidate, - AuthenticatedUser, - BuiltinESPrivileges, - EditUser, - FeaturesPrivileges, - InlineRoleTemplate, - InvalidRoleTemplate, - KibanaPrivileges, - RawKibanaFeaturePrivileges, - RawKibanaPrivileges, - Role, - RoleIndexPrivilege, - RoleKibanaPrivilege, - RoleMapping, - RoleTemplate, - StoredRoleTemplate, - User, - canUserChangePassword, - getUserDisplayName, -} from '../../../../plugins/security/common/model'; diff --git a/x-pack/legacy/plugins/security/index.d.ts b/x-pack/legacy/plugins/security/index.d.ts index 18284c8be689a1..d453415f73376c 100644 --- a/x-pack/legacy/plugins/security/index.d.ts +++ b/x-pack/legacy/plugins/security/index.d.ts @@ -5,7 +5,7 @@ */ import { Legacy } from 'kibana'; -import { AuthenticatedUser } from './common/model'; +import { AuthenticatedUser } from '../../../plugins/security/public'; /** * Public interface of the security plugin. diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index bc403b803b8df0..4988c30a1398b0 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -40,8 +40,6 @@ export const security = kibana => }, uiExports: { - chromeNavControls: [], - managementSections: ['plugins/security/views/management'], styleSheetPaths: resolve(__dirname, 'public/index.scss'), apps: [ { @@ -76,7 +74,6 @@ export const security = kibana => 'plugins/security/hacks/on_unauthorized_response', 'plugins/security/hacks/register_account_management_app', ], - home: ['plugins/security/register_feature'], injectDefaultVars: server => { const securityPlugin = server.newPlatform.setup.plugins.security; if (!securityPlugin) { diff --git a/x-pack/legacy/plugins/security/public/documentation_links.js b/x-pack/legacy/plugins/security/public/documentation_links.js deleted file mode 100644 index 8050289b95e9df..00000000000000 --- a/x-pack/legacy/plugins/security/public/documentation_links.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; - -const ES_REF_URL = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; - -export const documentationLinks = { - dashboardViewMode: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-view-modes.html`, - esClusterPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-cluster`, - esIndicesPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-indices`, - esRunAsPrivileges: `${ES_REF_URL}/security-privileges.html#_run_as_privilege`, -}; diff --git a/x-pack/legacy/plugins/security/public/images/logout.svg b/x-pack/legacy/plugins/security/public/images/logout.svg deleted file mode 100644 index d6533c07199040..00000000000000 --- a/x-pack/legacy/plugins/security/public/images/logout.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/images/person.svg b/x-pack/legacy/plugins/security/public/images/person.svg deleted file mode 100644 index 988ddac8859d71..00000000000000 --- a/x-pack/legacy/plugins/security/public/images/person.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/index.scss b/x-pack/legacy/plugins/security/public/index.scss index 2d7696bed39890..187ad5231534d2 100644 --- a/x-pack/legacy/plugins/security/public/index.scss +++ b/x-pack/legacy/plugins/security/public/index.scss @@ -15,3 +15,6 @@ $secFormWidth: 460px; // Public views @import './views/index'; +// Styles of Kibana Platform plugin +@import '../../../../plugins/security/public/index'; + diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js b/x-pack/legacy/plugins/security/public/lib/__tests__/util.js deleted file mode 100644 index 3f7d8aea53a85f..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { toggle, toggleSort } from '../../../public/lib/util'; - -describe('util', () => { - describe('toggle', () => { - it('should add an item to a collection if not already included', () => { - const collection = [1, 2, 3, 4, 5]; - toggle(collection, 6); - expect(collection.indexOf(6)).to.be.above(0); - }); - - it('should remove an item from a collection if already included', () => { - const collection = [1, 2, 3, 4, 5]; - toggle(collection, 3); - expect(collection.indexOf(3)).to.be.below(0); - }); - }); - - describe('toggleSort', () => { - it('should toggle reverse if called with the same orderBy', () => { - const sort = { orderBy: 'foo', reverse: false }; - - toggleSort(sort, 'foo'); - expect(sort.reverse).to.be.true; - - toggleSort(sort, 'foo'); - expect(sort.reverse).to.be.false; - }); - - it('should change orderBy and set reverse to false when called with a different orderBy', () => { - const sort = { orderBy: 'foo', reverse: false }; - - toggleSort(sort, 'bar'); - expect(sort.orderBy).to.equal('bar'); - expect(sort.reverse).to.be.false; - - sort.reverse = true; - toggleSort(sort, 'foo'); - expect(sort.orderBy).to.equal('foo'); - expect(sort.reverse).to.be.false; - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts deleted file mode 100644 index c5c6994bf4be36..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { kfetch } from 'ui/kfetch'; -import { Role, User, EditUser } from '../../common/model'; - -const usersUrl = '/internal/security/users'; -const rolesUrl = '/api/security/role'; - -export class UserAPIClient { - public async getUsers(): Promise { - return await kfetch({ pathname: usersUrl }); - } - - public async getUser(username: string): Promise { - const url = `${usersUrl}/${encodeURIComponent(username)}`; - return await kfetch({ pathname: url }); - } - - public async deleteUser(username: string) { - const url = `${usersUrl}/${encodeURIComponent(username)}`; - await kfetch({ pathname: url, method: 'DELETE' }, {}); - } - - public async saveUser(user: EditUser) { - const url = `${usersUrl}/${encodeURIComponent(user.username)}`; - - await kfetch({ pathname: url, body: JSON.stringify(user), method: 'POST' }); - } - - public async getRoles(): Promise { - return await kfetch({ pathname: rolesUrl }); - } - - public async getRole(name: string): Promise { - const url = `${rolesUrl}/${encodeURIComponent(name)}`; - return await kfetch({ pathname: url }); - } - - public async changePassword(username: string, password: string, currentPassword: string) { - const data: Record = { - newPassword: password, - }; - if (currentPassword) { - data.password = currentPassword; - } - await kfetch({ - pathname: `${usersUrl}/${encodeURIComponent(username)}/password`, - method: 'POST', - body: JSON.stringify(data), - }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts b/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts deleted file mode 100644 index fbc0460c5908a7..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { kfetch } from 'ui/kfetch'; -import { ApiKey, ApiKeyToInvalidate } from '../../common/model'; - -interface CheckPrivilegesResponse { - areApiKeysEnabled: boolean; - isAdmin: boolean; -} - -interface InvalidateApiKeysResponse { - itemsInvalidated: ApiKeyToInvalidate[]; - errors: any[]; -} - -interface GetApiKeysResponse { - apiKeys: ApiKey[]; -} - -const apiKeysUrl = `/internal/security/api_key`; - -export class ApiKeysApi { - public static async checkPrivileges(): Promise { - return kfetch({ pathname: `${apiKeysUrl}/privileges` }); - } - - public static async getApiKeys(isAdmin: boolean = false): Promise { - const query = { - isAdmin, - }; - - return kfetch({ pathname: apiKeysUrl, query }); - } - - public static async invalidateApiKeys( - apiKeys: ApiKeyToInvalidate[], - isAdmin: boolean = false - ): Promise { - const pathname = `${apiKeysUrl}/invalidate`; - const body = JSON.stringify({ apiKeys, isAdmin }); - return kfetch({ pathname, method: 'POST', body }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/role_utils.ts b/x-pack/legacy/plugins/security/public/lib/role_utils.ts deleted file mode 100644 index c33b7385306fb8..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/role_utils.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { cloneDeep, get } from 'lodash'; -import { Role } from '../../common/model'; - -/** - * Returns whether given role is enabled or not - * - * @param role Object Role JSON, as returned by roles API - * @return Boolean true if role is enabled; false otherwise - */ -export function isRoleEnabled(role: Partial) { - return get(role, 'transient_metadata.enabled', true); -} - -/** - * Returns whether given role is reserved or not. - * - * @param {role} the Role as returned by roles API - */ -export function isReservedRole(role: Partial) { - return get(role, 'metadata._reserved', false); -} - -/** - * Returns whether given role is editable through the UI or not. - * - * @param role the Role as returned by roles API - */ -export function isReadOnlyRole(role: Partial): boolean { - return isReservedRole(role) || !!(role._transform_error && role._transform_error.length > 0); -} - -/** - * Returns a deep copy of the role. - * - * @param role the Role to copy. - */ -export function copyRole(role: Role) { - return cloneDeep(role); -} - -/** - * Creates a deep copy of the role suitable for cloning. - * - * @param role the Role to clone. - */ -export function prepareRoleClone(role: Role): Role { - const clone = copyRole(role); - - clone.name = ''; - - return clone; -} diff --git a/x-pack/legacy/plugins/security/public/lib/roles_api.ts b/x-pack/legacy/plugins/security/public/lib/roles_api.ts deleted file mode 100644 index 20c1491ccaac69..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/roles_api.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { kfetch } from 'ui/kfetch'; -import { Role } from '../../common/model'; - -export class RolesApi { - public static async getRoles(): Promise { - return kfetch({ pathname: '/api/security/role' }); - } - - public static async getRole(roleName: string): Promise { - return kfetch({ pathname: `/api/security/role/${encodeURIComponent(roleName)}` }); - } - - public static async deleteRole(roleName: string) { - return kfetch({ - pathname: `/api/security/role/${encodeURIComponent(roleName)}`, - method: 'DELETE', - }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts b/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts deleted file mode 100644 index 861ba530050a13..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Role, RoleIndexPrivilege } from '../../common/model'; -import { isGlobalPrivilegeDefinition } from './privilege_utils'; - -export function transformRoleForSave(role: Role, spacesEnabled: boolean) { - // Remove any placeholder index privileges - role.elasticsearch.indices = role.elasticsearch.indices.filter( - indexPrivilege => !isPlaceholderPrivilege(indexPrivilege) - ); - - // Remove any placeholder query entries - role.elasticsearch.indices.forEach(index => index.query || delete index.query); - - // If spaces are disabled, then do not persist any space privileges - if (!spacesEnabled) { - role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition); - } - - role.kibana.forEach(kibanaPrivilege => { - // If a base privilege is defined, then do not persist feature privileges - if (kibanaPrivilege.base.length > 0) { - kibanaPrivilege.feature = {}; - } - }); - - delete role.name; - delete role.transient_metadata; - delete role._unrecognized_applications; - delete role._transform_error; - - return role; -} - -function isPlaceholderPrivilege(indexPrivilege: RoleIndexPrivilege) { - return indexPrivilege.names.length === 0; -} diff --git a/x-pack/legacy/plugins/security/public/lib/util.js b/x-pack/legacy/plugins/security/public/lib/util.js deleted file mode 100644 index bdf44aa3f10bb8..00000000000000 --- a/x-pack/legacy/plugins/security/public/lib/util.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function toggle(collection, item) { - const i = collection.indexOf(item); - if (i >= 0) collection.splice(i, 1); - else collection.push(item); -} - -export function toggleSort(sort, orderBy) { - if (sort.orderBy === orderBy) sort.reverse = !sort.reverse; - else { - sort.orderBy = orderBy; - sort.reverse = false; - } -} diff --git a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts b/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts deleted file mode 100644 index 91d98782dab423..00000000000000 --- a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { IHttpResponse } from 'angular'; -import chrome from 'ui/chrome'; - -const apiBase = chrome.addBasePath(`/internal/security/fields`); - -export async function getFields($http: any, query: string): Promise { - return await $http - .get(`${apiBase}/${query}`) - .then((response: IHttpResponse) => response.data || []); -} diff --git a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts b/x-pack/legacy/plugins/security/public/objects/lib/roles.ts deleted file mode 100644 index e33cbe4c6c031c..00000000000000 --- a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import chrome from 'ui/chrome'; -import { Role } from '../../../common/model'; -import { copyRole } from '../../lib/role_utils'; -import { transformRoleForSave } from '../../lib/transform_role_for_save'; - -const apiBase = chrome.addBasePath(`/api/security/role`); - -export async function saveRole($http: any, role: Role, spacesEnabled: boolean) { - const data = transformRoleForSave(copyRole(role), spacesEnabled); - - return await $http.put(`${apiBase}/${role.name}`, data); -} - -export async function deleteRole($http: any, name: string) { - return await $http.delete(`${apiBase}/${name}`); -} diff --git a/x-pack/legacy/plugins/security/public/register_feature.js b/x-pack/legacy/plugins/security/public/register_feature.js deleted file mode 100644 index c0bd42690b6fdb..00000000000000 --- a/x-pack/legacy/plugins/security/public/register_feature.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'security', - title: i18n.translate('xpack.security.registerFeature.securitySettingsTitle', { - defaultMessage: 'Security Settings', - }), - description: i18n.translate('xpack.security.registerFeature.securitySettingsDescription', { - defaultMessage: - 'Protect your data and easily manage who has access to what with users and roles.', - }), - icon: 'securityApp', - path: '/app/kibana#/management/security', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/services/shield_indices.js b/x-pack/legacy/plugins/security/public/services/shield_indices.js deleted file mode 100644 index 791fa6cb596488..00000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_indices.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', []); -module.service('shieldIndices', ($http, chrome) => { - return { - getFields: query => { - return $http - .get(chrome.addBasePath(`/internal/security/fields/${query}`)) - .then(response => response.data); - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/services/shield_role.js b/x-pack/legacy/plugins/security/public/services/shield_role.js deleted file mode 100644 index 261d3449a7a2d1..00000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_role.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'angular-resource'; -import { omit } from 'lodash'; -import angular from 'angular'; -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', ['ngResource']); -module.service('ShieldRole', ($resource, chrome) => { - return $resource( - chrome.addBasePath('/api/security/role/:name'), - { - name: '@name', - }, - { - save: { - method: 'PUT', - transformRequest(data) { - return angular.toJson( - omit(data, 'name', 'transient_metadata', '_unrecognized_applications') - ); - }, - }, - } - ); -}); diff --git a/x-pack/legacy/plugins/security/public/views/_index.scss b/x-pack/legacy/plugins/security/public/views/_index.scss index b85a7e19973906..6c2a091adf536e 100644 --- a/x-pack/legacy/plugins/security/public/views/_index.scss +++ b/x-pack/legacy/plugins/security/public/views/_index.scss @@ -1,5 +1,2 @@ // Login styles @import './login/index'; - -// Management styles -@import './management/index'; diff --git a/x-pack/legacy/plugins/security/public/views/account/account.html b/x-pack/legacy/plugins/security/public/views/account/account.html deleted file mode 100644 index 0935c415b18295..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/account/account.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js index 70a7b8dce727ec..13abc44e08f965 100644 --- a/x-pack/legacy/plugins/security/public/views/account/account.js +++ b/x-pack/legacy/plugins/security/public/views/account/account.js @@ -4,17 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import routes from 'ui/routes'; -import template from './account.html'; -import { i18n } from '@kbn/i18n'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { AccountManagementPage } from './components'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import routes from 'ui/routes'; routes.when('/account', { - template, + template: '
', k7Breadcrumbs: () => [ { text: i18n.translate('xpack.security.account.breadcrumb', { @@ -24,19 +21,15 @@ routes.when('/account', { ], controllerAs: 'accountController', controller($scope) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('userProfileReactRoot'); - if (elem) { - unmountComponentAtNode(elem); - } - }); $scope.$$postDigest(() => { + const domNode = document.getElementById('userProfileReactRoot'); + render( - - - , - document.getElementById('userProfileReactRoot') + , + domNode ); + + $scope.$on('$destroy', () => unmountComponentAtNode(domNode)); }); }, }); diff --git a/x-pack/legacy/plugins/security/public/views/login/_index.scss b/x-pack/legacy/plugins/security/public/views/login/_index.scss index 9f133940f79777..9083c8dc3b7751 100644 --- a/x-pack/legacy/plugins/security/public/views/login/_index.scss +++ b/x-pack/legacy/plugins/security/public/views/login/_index.scss @@ -5,5 +5,4 @@ // loginChart__legend--small // loginChart__legend-isLoading -@import 'login'; - +@import './components/index'; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss new file mode 100644 index 00000000000000..a6f9598b9cc043 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss @@ -0,0 +1 @@ +@import './login_page/index'; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx index 93451453a523ab..3a970d582bdc8d 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiCallOut } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; import { BasicLoginForm } from './basic_login_form'; const createMockHttp = ({ simulateError = false } = {}) => { diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx index e6d3b5b7536b6a..c263381fbdb564 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx @@ -9,7 +9,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, FormEvent, Fragment, MouseEvent } from 'react'; import ReactMarkdown from 'react-markdown'; import { EuiText } from '@elastic/eui'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; interface Props { http: any; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss new file mode 100644 index 00000000000000..4dd2c0cabfb5e8 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss @@ -0,0 +1 @@ +@import './login_page'; diff --git a/x-pack/legacy/plugins/security/public/views/login/_login.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss similarity index 88% rename from x-pack/legacy/plugins/security/public/views/login/_login.scss rename to x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss index 607e9e6ec5e3f3..cdfad55ee064af 100644 --- a/x-pack/legacy/plugins/security/public/views/login/_login.scss +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss @@ -1,4 +1,3 @@ - .loginWelcome { @include kibanaFullScreenGraphics; } @@ -16,10 +15,6 @@ margin-bottom: $euiSizeXL; } -.loginWelcome__footerAction { - margin-right: $euiSizeS; -} - .loginWelcome__content { position: relative; margin: auto; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx index c16db007bdbdcf..a0318d50a45e58 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { LoginLayout, LoginState } from '../../../../../common/login_state'; +import { LoginLayout, LoginState } from '../../login_state'; import { LoginPage } from './login_page'; const createMockHttp = ({ simulateError = false } = {}) => { diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx index e7e56947ca58f8..8035789a30e9df 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx @@ -19,7 +19,7 @@ import { EuiTitle, } from '@elastic/eui'; import classNames from 'classnames'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; import { BasicLoginForm } from '../basic_login_form'; import { DisabledLoginForm } from '../disabled_login_form'; diff --git a/x-pack/legacy/plugins/security/public/views/login/login.html b/x-pack/legacy/plugins/security/public/views/login/login.html deleted file mode 100644 index 2695fabdd63671..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/login/login.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/login/login.tsx b/x-pack/legacy/plugins/security/public/views/login/login.tsx index d9daf2d1f4d0de..0b89ac553c9a88 100644 --- a/x-pack/legacy/plugins/security/public/views/login/login.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/login.tsx @@ -6,16 +6,14 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import { parseNext } from 'plugins/security/lib/parse_next'; import { LoginPage } from 'plugins/security/views/login/components'; -// @ts-ignore -import template from 'plugins/security/views/login/login.html'; import React from 'react'; import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { parse } from 'url'; -import { LoginState } from '../../../common/login_state'; +import { parseNext } from './parse_next'; +import { LoginState } from './login_state'; const messageMap = { SESSION_EXPIRED: i18n.translate('xpack.security.login.sessionExpiredDescription', { defaultMessage: 'Your session has timed out. Please log in again.', @@ -31,7 +29,7 @@ interface AnyObject { (chrome as AnyObject) .setVisible(false) - .setRootTemplate(template) + .setRootTemplate('
') .setRootController( 'login', ( diff --git a/x-pack/legacy/plugins/security/common/login_state.ts b/x-pack/legacy/plugins/security/public/views/login/login_state.ts similarity index 100% rename from x-pack/legacy/plugins/security/common/login_state.ts rename to x-pack/legacy/plugins/security/public/views/login/login_state.ts diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts similarity index 80% rename from x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js rename to x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts index 7516433c77f83d..b5e6c7dca41d8f 100644 --- a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js +++ b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { parseNext } from '../parse_next'; +import { parseNext } from './parse_next'; describe('parseNext', () => { it('should return a function', () => { - expect(parseNext).to.be.a('function'); + expect(parseNext).toBeInstanceOf(Function); }); describe('with basePath defined', () => { @@ -17,14 +16,14 @@ describe('parseNext', () => { it('should return basePath with a trailing slash when next is not specified', () => { const basePath = '/iqf'; const href = `${basePath}/login`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); it('should properly handle next without hash', () => { const basePath = '/iqf'; const next = `${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(next); + expect(parseNext(href, basePath)).toEqual(next); }); it('should properly handle next with hash', () => { @@ -32,7 +31,7 @@ describe('parseNext', () => { const next = `${basePath}/app/kibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${next}#${hash}`); + expect(parseNext(href, basePath)).toEqual(`${next}#${hash}`); }); it('should properly decode special characters', () => { @@ -40,7 +39,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(basePath)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(decodeURIComponent(`${next}#${hash}`)); + expect(parseNext(href, basePath)).toEqual(decodeURIComponent(`${next}#${hash}`)); }); // to help prevent open redirect to a different url @@ -48,7 +47,7 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = `https://example.com${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different url by abusing encodings @@ -58,7 +57,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different port @@ -66,7 +65,7 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = `http://localhost:5601${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different port by abusing encodings @@ -76,7 +75,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different base path @@ -84,18 +83,18 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = '/notbasepath/app/kibana'; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // disallow network-path references it('should return / if next is url without protocol', () => { const nextWithTwoSlashes = '//example.com'; const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`; - expect(parseNext(hrefWithTwoSlashes)).to.equal('/'); + expect(parseNext(hrefWithTwoSlashes)).toEqual('/'); const nextWithThreeSlashes = '///example.com'; const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`; - expect(parseNext(hrefWithThreeSlashes)).to.equal('/'); + expect(parseNext(hrefWithThreeSlashes)).toEqual('/'); }); }); @@ -103,34 +102,34 @@ describe('parseNext', () => { // trailing slash is important since it must match the cookie path exactly it('should return / with a trailing slash when next is not specified', () => { const href = '/login'; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); it('should properly handle next without hash', () => { const next = '/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal(next); + expect(parseNext(href)).toEqual(next); }); it('should properly handle next with hash', () => { const next = '/app/kibana'; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal(`${next}#${hash}`); + expect(parseNext(href)).toEqual(`${next}#${hash}`); }); it('should properly decode special characters', () => { const next = '%2Fapp%2Fkibana'; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal(decodeURIComponent(`${next}#${hash}`)); + expect(parseNext(href)).toEqual(decodeURIComponent(`${next}#${hash}`)); }); // to help prevent open redirect to a different url it('should return / if next includes a protocol/hostname', () => { const next = 'https://example.com/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different url by abusing encodings @@ -139,14 +138,14 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different port it('should return / if next includes a port', () => { const next = 'http://localhost:5601/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different port by abusing encodings @@ -155,18 +154,18 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // disallow network-path references it('should return / if next is url without protocol', () => { const nextWithTwoSlashes = '//example.com'; const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`; - expect(parseNext(hrefWithTwoSlashes)).to.equal('/'); + expect(parseNext(hrefWithTwoSlashes)).toEqual('/'); const nextWithThreeSlashes = '///example.com'; const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`; - expect(parseNext(hrefWithThreeSlashes)).to.equal('/'); + expect(parseNext(hrefWithThreeSlashes)).toEqual('/'); }); }); }); diff --git a/x-pack/legacy/plugins/security/public/lib/parse_next.ts b/x-pack/legacy/plugins/security/public/views/login/parse_next.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/lib/parse_next.ts rename to x-pack/legacy/plugins/security/public/views/login/parse_next.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/_index.scss b/x-pack/legacy/plugins/security/public/views/management/_index.scss deleted file mode 100644 index 78b53845071e4a..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './change_password_form/index'; -@import './edit_role/index'; -@import './edit_user/index'; -@import './role_mappings/edit_role_mapping/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html deleted file mode 100644 index e46c6f72b5d20e..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js deleted file mode 100644 index e7143b10208148..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from './api_keys.html'; -import { API_KEYS_PATH } from '../management_urls'; -import { getApiKeysBreadcrumbs } from '../breadcrumbs'; -import { I18nContext } from 'ui/i18n'; -import { ApiKeysGridPage } from './components'; - -routes.when(API_KEYS_PATH, { - template, - k7Breadcrumbs: getApiKeysBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('apiKeysGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts b/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts deleted file mode 100644 index 4ab7e45e848498..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; - -export function getUsersBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.users.breadcrumb', { - defaultMessage: 'Users', - }), - href: '#/management/security/users', - }, - ]; -} - -export function getEditUserBreadcrumbs($route: Record) { - const { username } = $route.current.params; - return [ - ...getUsersBreadcrumbs(), - { - text: username, - href: `#/management/security/users/edit/${username}`, - }, - ]; -} - -export function getCreateUserBreadcrumbs() { - return [ - ...getUsersBreadcrumbs(), - { - text: i18n.translate('xpack.security.users.createBreadcrumb', { - defaultMessage: 'Create', - }), - }, - ]; -} - -export function getRolesBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.roles.breadcrumb', { - defaultMessage: 'Roles', - }), - href: '#/management/security/roles', - }, - ]; -} - -export function getEditRoleBreadcrumbs($route: Record) { - const { name } = $route.current.params; - return [ - ...getRolesBreadcrumbs(), - { - text: name, - href: `#/management/security/roles/edit/${name}`, - }, - ]; -} - -export function getCreateRoleBreadcrumbs() { - return [ - ...getUsersBreadcrumbs(), - { - text: i18n.translate('xpack.security.roles.createBreadcrumb', { - defaultMessage: 'Create', - }), - }, - ]; -} - -export function getApiKeysBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.apiKeys.breadcrumb', { - defaultMessage: 'API Keys', - }), - href: '#/management/security/api_keys', - }, - ]; -} - -export function getRoleMappingBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.roleMapping.breadcrumb', { - defaultMessage: 'Role Mappings', - }), - href: '#/management/security/role_mappings', - }, - ]; -} - -export function getEditRoleMappingBreadcrumbs($route: Record) { - const { name } = $route.current.params; - return [ - ...getRoleMappingBreadcrumbs(), - { - text: - name || - i18n.translate('xpack.security.roleMappings.createBreadcrumb', { - defaultMessage: 'Create', - }), - href: `#/management/security/role_mappings/edit/${name}`, - }, - ]; -} diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss deleted file mode 100644 index 98331c2070a31e..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss +++ /dev/null @@ -1,17 +0,0 @@ -.secChangePasswordForm__panel { - max-width: $secFormWidth; -} - -.secChangePasswordForm__subLabel { - margin-bottom: $euiSizeS; -} - -.secChangePasswordForm__footer { - display: flex; - justify-content: flex-start; - align-items: center; - - .kuiButton + .kuiButton { - margin-left: $euiSizeS; - } -} diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss deleted file mode 100644 index a6058b5ddebbf0..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './change_password_form'; diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html deleted file mode 100644 index 92fb95861a6f8e..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html +++ /dev/null @@ -1,141 +0,0 @@ - - -
- - - - - - -
- -
- - - - -
-
- - -
- - - - -
-
- - -
- - - - -
- - -
- - -
-
- - - -
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js deleted file mode 100644 index d9aa59f6df1427..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; -import template from './change_password_form.html'; - -const module = uiModules.get('security', ['kibana']); -module.directive('kbnChangePasswordForm', function() { - return { - template, - scope: { - requireCurrentPassword: '=', - showKibanaWarning: '=', - onChangePassword: '&', - }, - restrict: 'E', - replace: true, - controllerAs: 'changePasswordController', - controller: function($scope) { - this.currentPassword = null; - this.newPassword = null; - this.newPasswordConfirmation = null; - this.isFormVisible = false; - this.isIncorrectPassword = false; - - this.showForm = () => { - this.isFormVisible = true; - }; - - this.hideForm = () => { - $scope.changePasswordForm.$setPristine(); - $scope.changePasswordForm.$setUntouched(); - this.currentPassword = null; - this.newPassword = null; - this.newPasswordConfirmation = null; - this.isFormVisible = false; - this.isIncorrectPassword = false; - }; - - this.onIncorrectPassword = () => { - this.isIncorrectPassword = true; - }; - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss deleted file mode 100644 index 192091fb04e3c9..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/index'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss deleted file mode 100644 index 32b3832e7a9fa9..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import './collapsible_panel/collapsible_panel'; -@import './privileges/kibana/space_aware_privilege_section/index'; -@import './privileges/kibana/feature_table/index'; -@import './spaces_popover_list/spaces_popover_list'; - -.secPrivilegeFeatureIcon { - flex-shrink: 0; - margin-right: $euiSizeS; -} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx deleted file mode 100644 index 67c32c8393171f..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ReactWrapper } from 'enzyme'; -import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { UICapabilities } from 'ui/capabilities'; -import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/public'; -// These modules should be moved into a common directory -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Actions } from '../../../../../../../../plugins/security/server/authorization/actions'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { privilegesFactory } from '../../../../../../../../plugins/security/server/authorization/privileges'; -import { RawKibanaPrivileges, Role } from '../../../../../common/model'; -import { EditRolePage } from './edit_role_page'; -import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; -import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section'; -import { TransformErrorSection } from './privileges/kibana/transform_error_section'; - -const buildFeatures = () => { - return [ - { - id: 'feature1', - name: 'Feature 1', - icon: 'addDataApp', - app: ['feature1App'], - privileges: { - all: { - app: ['feature1App'], - ui: ['feature1-ui'], - savedObject: { - all: [], - read: [], - }, - }, - }, - }, - { - id: 'feature2', - name: 'Feature 2', - icon: 'addDataApp', - app: ['feature2App'], - privileges: { - all: { - app: ['feature2App'], - ui: ['feature2-ui'], - savedObject: { - all: ['feature2'], - read: ['config'], - }, - }, - }, - }, - ] as Feature[]; -}; - -const buildRawKibanaPrivileges = () => { - return privilegesFactory(new Actions('unit_test_version'), { - getFeatures: () => buildFeatures(), - }).get(); -}; - -const buildBuiltinESPrivileges = () => { - return { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }; -}; - -const buildUICapabilities = (canManageSpaces = true) => { - return { - catalogue: {}, - management: {}, - navLinks: {}, - spaces: { - manage: canManageSpaces, - }, - } as UICapabilities; -}; - -const buildSpaces = () => { - return [ - { - id: 'default', - name: 'Default', - disabledFeatures: [], - _reserved: true, - }, - { - id: 'space_1', - name: 'Space 1', - disabledFeatures: [], - }, - { - id: 'space_2', - name: 'Space 2', - disabledFeatures: ['feature2'], - }, - ] as Space[]; -}; - -const expectReadOnlyFormButtons = (wrapper: ReactWrapper) => { - expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(1); - expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(0); -}; - -const expectSaveFormButtons = (wrapper: ReactWrapper) => { - expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(0); - expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(1); -}; - -describe('', () => { - describe('with spaces enabled', () => { - it('can render a reserved role', () => { - const role: Role = { - name: 'superuser', - metadata: { - _reserved: true, - }, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectReadOnlyFormButtons(wrapper); - }); - - it('can render a user defined role', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when creating a new role', () => { - // @ts-ignore - const role: Role = { - metadata: {}, - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when cloning an existing role', () => { - const role: Role = { - metadata: { - _reserved: false, - }, - name: '', - elasticsearch: { - cluster: ['all', 'manage'], - indices: [ - { - names: ['foo*'], - privileges: ['all'], - field_security: { - except: ['f'], - grant: ['b*'], - }, - }, - ], - run_as: ['elastic'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('renders an auth error when not authorized to manage spaces', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - - expect( - wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') - ).toHaveLength(1); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('renders a partial read-only view when there is a transform error', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [], - _transform_error: ['kibana'], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(TransformErrorSection)).toHaveLength(1); - expectReadOnlyFormButtons(wrapper); - }); - }); - - describe('with spaces disabled', () => { - it('can render a reserved role', () => { - const role: Role = { - name: 'superuser', - metadata: { - _reserved: true, - }, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectReadOnlyFormButtons(wrapper); - }); - - it('can render a user defined role', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when creating a new role', () => { - // @ts-ignore - const role: Role = { - metadata: {}, - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('can render when cloning an existing role', () => { - const role: Role = { - metadata: { - _reserved: false, - }, - name: '', - elasticsearch: { - cluster: ['all', 'manage'], - indices: [ - { - names: ['foo*'], - privileges: ['all'], - field_security: { - except: ['f'], - grant: ['b*'], - }, - }, - ], - run_as: ['elastic'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('does not care if user cannot manage spaces', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - - expect( - wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') - ).toHaveLength(0); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('renders a partial read-only view when there is a transform error', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [], - _transform_error: ['kibana'], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(TransformErrorSection)).toHaveLength(1); - expectReadOnlyFormButtons(wrapper); - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx deleted file mode 100644 index 2ba012afa689dc..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiButton, - EuiButtonEmpty, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { get } from 'lodash'; -import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { toastNotifications } from 'ui/notify'; -import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { - KibanaPrivileges, - RawKibanaPrivileges, - Role, - BuiltinESPrivileges, -} from '../../../../../common/model'; -import { - isReadOnlyRole, - isReservedRole, - copyRole, - prepareRoleClone, -} from '../../../../lib/role_utils'; -import { deleteRole, saveRole } from '../../../../objects'; -import { ROLES_PATH } from '../../management_urls'; -import { RoleValidationResult, RoleValidator } from '../lib/validate_role'; -import { DeleteRoleButton } from './delete_role_button'; -import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; -import { ReservedRoleBadge } from './reserved_role_badge'; - -interface Props { - action: 'edit' | 'clone'; - role: Role; - runAsUsers: string[]; - indexPatterns: string[]; - httpClient: any; - allowDocumentLevelSecurity: boolean; - allowFieldLevelSecurity: boolean; - kibanaPrivileges: RawKibanaPrivileges; - builtinESPrivileges: BuiltinESPrivileges; - spaces?: Space[]; - spacesEnabled: boolean; - intl: InjectedIntl; - uiCapabilities: UICapabilities; - features: Feature[]; -} - -interface State { - role: Role; - formError: RoleValidationResult | null; -} - -class EditRolePageUI extends Component { - private validator: RoleValidator; - - constructor(props: Props) { - super(props); - - this.validator = new RoleValidator({ shouldValidate: false }); - - let role: Role; - if (props.action === 'clone') { - role = prepareRoleClone(props.role); - } else { - role = copyRole(props.role); - } - - this.state = { - role, - formError: null, - }; - } - - public componentDidMount() { - if (this.props.action === 'clone' && isReservedRole(this.props.role)) { - this.backToRoleList(); - } - } - - public render() { - const description = this.props.spacesEnabled ? ( - - ) : ( - - ); - - return ( -
- - {this.getFormTitle()} - - - - {description} - - {isReservedRole(this.state.role) && ( - - - -

- -

-
-
- )} - - - - {this.getRoleName()} - - {this.getElasticsearchPrivileges()} - - {this.getKibanaPrivileges()} - - - - {this.getFormButtons()} -
-
- ); - } - - private getFormTitle = () => { - let titleText; - const props: HTMLProps = { - tabIndex: 0, - }; - if (isReservedRole(this.state.role)) { - titleText = ( - - ); - props['aria-describedby'] = 'reservedRoleDescription'; - } else if (this.editingExistingRole()) { - titleText = ( - - ); - } else { - titleText = ( - - ); - } - - return ( - -

- {titleText} -

-
- ); - }; - - private getActionButton = () => { - if (this.editingExistingRole() && !isReadOnlyRole(this.state.role)) { - return ( - - - - ); - } - - return null; - }; - - private getRoleName = () => { - return ( - - - } - helpText={ - !isReservedRole(this.state.role) && this.editingExistingRole() ? ( - - ) : ( - undefined - ) - } - {...this.validator.validateRoleName(this.state.role)} - > - - - - ); - }; - - private onNameChange = (e: ChangeEvent) => { - const rawValue = e.target.value; - const name = rawValue.replace(/\s/g, '_'); - - this.setState({ - role: { - ...this.state.role, - name, - }, - }); - }; - - private getElasticsearchPrivileges() { - return ( -
- - -
- ); - } - - private onRoleChange = (role: Role) => { - this.setState({ - role, - }); - }; - - private getKibanaPrivileges = () => { - return ( -
- - -
- ); - }; - - private getFormButtons = () => { - if (isReadOnlyRole(this.state.role)) { - return this.getReturnToRoleListButton(); - } - - return ( - - {this.getSaveButton()} - {this.getCancelButton()} - - {this.getActionButton()} - - ); - }; - - private getReturnToRoleListButton = () => { - return ( - - - - ); - }; - - private getSaveButton = () => { - const saveText = this.editingExistingRole() ? ( - - ) : ( - - ); - - return ( - - {saveText} - - ); - }; - - private getCancelButton = () => { - return ( - - - - ); - }; - - private editingExistingRole = () => { - return !!this.props.role.name && this.props.action === 'edit'; - }; - - private saveRole = () => { - this.validator.enableValidation(); - - const result = this.validator.validateForSave(this.state.role); - if (result.isInvalid) { - this.setState({ - formError: result, - }); - } else { - this.setState({ - formError: null, - }); - - const { httpClient, intl, spacesEnabled } = this.props; - - saveRole(httpClient, this.state.role, spacesEnabled) - .then(() => { - toastNotifications.addSuccess( - intl.formatMessage({ - id: 'xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage', - defaultMessage: 'Saved role', - }) - ); - this.backToRoleList(); - }) - .catch((error: any) => { - toastNotifications.addDanger(get(error, 'data.message')); - }); - } - }; - - private handleDeleteRole = () => { - const { httpClient, role, intl } = this.props; - - deleteRole(httpClient, role.name) - .then(() => { - toastNotifications.addSuccess( - intl.formatMessage({ - id: 'xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage', - defaultMessage: 'Deleted role', - }) - ); - this.backToRoleList(); - }) - .catch((error: any) => { - toastNotifications.addDanger(get(error, 'data.message')); - }); - }; - - private backToRoleList = () => { - window.location.hash = ROLES_PATH; - }; -} - -export const EditRolePage = injectI18n(EditRolePageUI); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx deleted file mode 100644 index 5ba3d1daf61acb..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { RoleValidator } from '../../../lib/validate_role'; -import { ClusterPrivileges } from './cluster_privileges'; -import { ElasticsearchPrivileges } from './elasticsearch_privileges'; -import { IndexPrivileges } from './index_privileges'; - -test('it renders without crashing', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); -}); - -test('it renders ClusterPrivileges', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = mountWithIntl(); - expect(wrapper.find(ClusterPrivileges)).toHaveLength(1); -}); - -test('it renders IndexPrivileges', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = mountWithIntl(); - expect(wrapper.find(IndexPrivileges)).toHaveLength(1); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html b/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html deleted file mode 100644 index ca4073dcad6f5c..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js deleted file mode 100644 index 27c9beb4ba8284..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import routes from 'ui/routes'; -import { capabilities } from 'ui/capabilities'; -import { kfetch } from 'ui/kfetch'; -import { fatalError, toastNotifications } from 'ui/notify'; -import { npStart } from 'ui/new_platform'; -import template from 'plugins/security/views/management/edit_role/edit_role.html'; -import 'plugins/security/services/shield_role'; -import 'plugins/security/services/shield_indices'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { UserAPIClient } from '../../../lib/api'; -import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; -import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; - -import { EditRolePage } from './components'; - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { i18n } from '@kbn/i18n'; - -const routeDefinition = action => ({ - template, - k7Breadcrumbs: ($injector, $route) => - $injector.invoke( - action === 'edit' && $route.current.params.name - ? getEditRoleBreadcrumbs - : getCreateRoleBreadcrumbs - ), - resolve: { - role($route, ShieldRole, Promise, kbnUrl) { - const name = $route.current.params.name; - - let role; - - if (name != null) { - role = ShieldRole.get({ name }).$promise.catch(response => { - if (response.status === 404) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.security.management.roles.roleNotFound', { - defaultMessage: 'No "{roleName}" role found.', - values: { roleName: name }, - }), - }); - kbnUrl.redirect(ROLES_PATH); - } else { - return fatalError(response); - } - }); - } else { - role = Promise.resolve( - new ShieldRole({ - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - _unrecognized_applications: [], - }) - ); - } - - return role.then(res => res.toJSON()); - }, - users() { - return new UserAPIClient().getUsers().then(users => _.map(users, 'username')); - }, - indexPatterns() { - return npStart.plugins.data.indexPatterns.getTitles(); - }, - spaces(spacesEnabled) { - if (spacesEnabled) { - return kfetch({ method: 'get', pathname: '/api/spaces/space' }); - } - return []; - }, - kibanaPrivileges() { - return kfetch({ - method: 'get', - pathname: '/api/security/privileges', - query: { includeActions: true }, - }); - }, - builtinESPrivileges() { - return kfetch({ method: 'get', pathname: '/internal/security/esPrivileges/builtin' }); - }, - features() { - return kfetch({ method: 'get', pathname: '/api/features' }).catch(e => { - // TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app. - const unauthorizedForFeatures = _.get(e, 'body.statusCode') === 404; - if (unauthorizedForFeatures) { - return []; - } - throw e; - }); - }, - }, - controllerAs: 'editRole', - controller($injector, $scope, $http, enableSpaceAwarePrivileges) { - const $route = $injector.get('$route'); - const role = $route.current.locals.role; - - const allowDocumentLevelSecurity = xpackInfo.get( - 'features.security.allowRoleDocumentLevelSecurity' - ); - const allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity'); - if (role.elasticsearch.indices.length === 0) { - const emptyOption = { - names: [], - privileges: [], - }; - - if (allowFieldLevelSecurity) { - emptyOption.field_security = { - grant: ['*'], - except: [], - }; - } - - if (allowDocumentLevelSecurity) { - emptyOption.query = ''; - } - - role.elasticsearch.indices.push(emptyOption); - } - - const { - users, - indexPatterns, - spaces, - kibanaPrivileges, - builtinESPrivileges, - features, - } = $route.current.locals; - - $scope.$$postDigest(async () => { - const domNode = document.getElementById('editRoleReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); - -routes.when(`${CLONE_ROLES_PATH}/:name`, routeDefinition('clone')); -routes.when(`${EDIT_ROLES_PATH}/:name?`, routeDefinition('edit')); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss deleted file mode 100644 index c5da74aa3f785c..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './users'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html deleted file mode 100644 index 4fa2768480874a..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js deleted file mode 100644 index ab218022c6ee64..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import routes from 'ui/routes'; -import template from 'plugins/security/views/management/edit_user/edit_user.html'; -import 'angular-resource'; -import 'ui/angular_ui_select'; -import 'plugins/security/services/shield_role'; -import { EDIT_USERS_PATH } from '../management_urls'; -import { EditUserPage } from './components'; -import { UserAPIClient } from '../../../lib/api'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs'; - -const renderReact = (elem, changeUrl, username) => { - render( - - - , - elem - ); -}; - -routes.when(`${EDIT_USERS_PATH}/:username?`, { - template, - k7Breadcrumbs: ($injector, $route) => - $injector.invoke( - $route.current.params.username ? getEditUserBreadcrumbs : getCreateUserBreadcrumbs - ), - controllerAs: 'editUser', - controller($scope, $route, kbnUrl) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('editUserReactRoot'); - if (elem) { - unmountComponentAtNode(elem); - } - }); - $scope.$$postDigest(() => { - const elem = document.getElementById('editUserReactRoot'); - const username = $route.current.params.username; - const changeUrl = url => { - kbnUrl.change(url); - $scope.$apply(); - }; - renderReact(elem, changeUrl, username); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js deleted file mode 100644 index f0369f232aeba8..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/management.js +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'plugins/security/views/management/change_password_form/change_password_form'; -import 'plugins/security/views/management/password_form/password_form'; -import 'plugins/security/views/management/users_grid/users'; -import 'plugins/security/views/management/roles_grid/roles'; -import 'plugins/security/views/management/api_keys_grid/api_keys'; -import 'plugins/security/views/management/edit_user/edit_user'; -import 'plugins/security/views/management/edit_role/index'; -import 'plugins/security/views/management/role_mappings/role_mappings_grid'; -import 'plugins/security/views/management/role_mappings/edit_role_mapping'; -import routes from 'ui/routes'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { ROLES_PATH, USERS_PATH, API_KEYS_PATH, ROLE_MAPPINGS_PATH } from './management_urls'; - -import { management } from 'ui/management'; -import { npSetup } from 'ui/new_platform'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; - -routes - .defaults(/^\/management\/security(\/|$)/, { - resolve: { - showLinks(kbnUrl, Promise) { - if (!xpackInfo.get('features.security.showLinks')) { - toastNotifications.addDanger({ - title: xpackInfo.get('features.security.linksMessage'), - }); - kbnUrl.redirect('/management'); - return Promise.halt(); - } - }, - }, - }) - .defaults(/\/management/, { - resolve: { - securityManagementSection: function() { - const showSecurityLinks = xpackInfo.get('features.security.showLinks'); - const showRoleMappingsManagementLink = xpackInfo.get( - 'features.security.showRoleMappingsManagement' - ); - - function deregisterSecurity() { - management.deregister('security'); - } - - function deregisterRoleMappingsManagement() { - if (management.hasItem('security')) { - const security = management.getSection('security'); - if (security.hasItem('roleMappings')) { - security.deregister('roleMappings'); - } - } - } - - function ensureSecurityRegistered() { - const registerSecurity = () => - management.register('security', { - display: i18n.translate('xpack.security.management.securityTitle', { - defaultMessage: 'Security', - }), - order: 100, - icon: 'securityApp', - }); - const getSecurity = () => management.getSection('security'); - - const security = management.hasItem('security') ? getSecurity() : registerSecurity(); - - if (!security.hasItem('users')) { - security.register('users', { - name: 'securityUsersLink', - order: 10, - display: i18n.translate('xpack.security.management.usersTitle', { - defaultMessage: 'Users', - }), - url: `#${USERS_PATH}`, - }); - } - - if (!security.hasItem('roles')) { - security.register('roles', { - name: 'securityRolesLink', - order: 20, - display: i18n.translate('xpack.security.management.rolesTitle', { - defaultMessage: 'Roles', - }), - url: `#${ROLES_PATH}`, - }); - } - - if (!security.hasItem('apiKeys')) { - security.register('apiKeys', { - name: 'securityApiKeysLink', - order: 30, - display: i18n.translate('xpack.security.management.apiKeysTitle', { - defaultMessage: 'API Keys', - }), - url: `#${API_KEYS_PATH}`, - }); - } - - if (showRoleMappingsManagementLink && !security.hasItem('roleMappings')) { - security.register('roleMappings', { - name: 'securityRoleMappingLink', - order: 30, - display: i18n.translate('xpack.security.management.roleMappingsTitle', { - defaultMessage: 'Role Mappings', - }), - url: `#${ROLE_MAPPINGS_PATH}`, - }); - } - } - - if (!showSecurityLinks) { - deregisterSecurity(); - } else { - if (!showRoleMappingsManagementLink) { - deregisterRoleMappingsManagement(); - } - - // getCurrentUser will reject if there is no authenticated user, so we prevent them from - // seeing the security management screens. - return npSetup.plugins.security.authc - .getCurrentUser() - .then(ensureSecurityRegistered) - .catch(deregisterSecurity); - } - }, - }, - }); diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html deleted file mode 100644 index 72956992100f5e..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html +++ /dev/null @@ -1,53 +0,0 @@ - - -
- - - - -
-
- - -
- - - - -
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js deleted file mode 100644 index edcccdb5e6e697..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; -import template from './password_form.html'; - -const module = uiModules.get('security', ['kibana']); -module.directive('kbnPasswordForm', function() { - return { - template, - scope: { - password: '=', - }, - restrict: 'E', - replace: true, - controllerAs: 'passwordController', - controller: function() { - this.confirmation = null; - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss deleted file mode 100644 index 80e08ebcf12267..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/rule_editor_panel/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx deleted file mode 100644 index 375a8d9f374a86..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { findTestSubject } from 'test_utils/find_test_subject'; - -// brace/ace uses the Worker class, which is not currently provided by JSDOM. -// This is not required for the tests to pass, but it rather suppresses lengthy -// warnings in the console which adds unnecessary noise to the test output. -import 'test_utils/stub_web_worker'; - -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; -import { EditRoleMappingPage } from '.'; -import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../../components'; -import { VisualRuleEditor } from './rule_editor_panel/visual_rule_editor'; -import { JSONRuleEditor } from './rule_editor_panel/json_rule_editor'; -import { EuiComboBox } from '@elastic/eui'; - -jest.mock('../../../../../lib/roles_api', () => { - return { - RolesApi: { - getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), - }, - }; -}); - -describe('EditRoleMappingPage', () => { - it('allows a role mapping to be created', async () => { - const roleMappingsAPI = ({ - saveRoleMapping: jest.fn().mockResolvedValue(null), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - - await nextTick(); - wrapper.update(); - - findTestSubject(wrapper, 'roleMappingFormNameInput').simulate('change', { - target: { value: 'my-role-mapping' }, - }); - - (wrapper - .find(EuiComboBox) - .filter('[data-test-subj="roleMappingFormRoleComboBox"]') - .props() as any).onChange([{ label: 'foo_role' }]); - - findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); - - findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); - - expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ - name: 'my-role-mapping', - enabled: true, - roles: ['foo_role'], - role_templates: [], - rules: { - all: [{ field: { username: '*' } }], - }, - metadata: {}, - }); - }); - - it('allows a role mapping to be updated', async () => { - const roleMappingsAPI = ({ - saveRoleMapping: jest.fn().mockResolvedValue(null), - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { id: 'foo' }, - }, - ], - enabled: true, - rules: { - any: [{ field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }], - }, - metadata: { - foo: 'bar', - bar: 'baz', - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - findTestSubject(wrapper, 'switchToRolesButton').simulate('click'); - - (wrapper - .find(EuiComboBox) - .filter('[data-test-subj="roleMappingFormRoleComboBox"]') - .props() as any).onChange([{ label: 'foo_role' }]); - - findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); - wrapper.find('button[id="addRuleOption"]').simulate('click'); - - findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); - - expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ - name: 'foo', - enabled: true, - roles: ['foo_role'], - role_templates: [], - rules: { - any: [ - { field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }, - { field: { username: '*' } }, - ], - }, - metadata: { - foo: 'bar', - bar: 'baz', - }, - }); - }); - - it('renders a permission denied message when unauthorized to manage role mappings', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: false, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(PermissionDenied)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(PermissionDenied)).toHaveLength(1); - }); - - it('renders a warning when there are no compatible realms enabled', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); - }); - - it('renders a warning when editing a mapping with a stored role template, when stored scripts are disabled', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { id: 'foo' }, - }, - ], - enabled: true, - rules: { - field: { username: '*' }, - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1); - }); - - it('renders a warning when editing a mapping with an inline role template, when inline scripts are disabled', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { source: 'foo' }, - }, - ], - enabled: true, - rules: { - field: { username: '*' }, - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: false, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - }); - - it('renders the visual editor by default for simple rule sets', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - roles: ['superuser'], - enabled: true, - rules: { - all: [ - { - field: { - username: '*', - }, - }, - { - field: { - dn: null, - }, - }, - { - field: { - realm: ['ldap', 'pki', null, 12], - }, - }, - ], - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); - expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); - }); - - it('renders the JSON editor by default for complex rule sets', async () => { - const createRule = (depth: number): Record => { - if (depth > 0) { - const rule = { - all: [ - { - field: { - username: '*', - }, - }, - ], - } as Record; - - const subRule = createRule(depth - 1); - if (subRule) { - rule.all.push(subRule); - } - - return rule; - } - return null as any; - }; - - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - roles: ['superuser'], - enabled: true, - rules: createRule(10), - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); - expect(wrapper.find(JSONRuleEditor)).toHaveLength(1); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html deleted file mode 100644 index ca8ab9c35c49ba..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx deleted file mode 100644 index b064a4dc50a228..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; -// @ts-ignore -import template from './edit_role_mapping.html'; -import { CREATE_ROLE_MAPPING_PATH } from '../../management_urls'; -import { getEditRoleMappingBreadcrumbs } from '../../breadcrumbs'; -import { EditRoleMappingPage } from './components'; - -routes.when(`${CREATE_ROLE_MAPPING_PATH}/:name?`, { - template, - k7Breadcrumbs: getEditRoleMappingBreadcrumbs, - controller($scope, $route) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('editRoleMappingReactRoot'); - - const { name } = $route.current.params; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx deleted file mode 100644 index 259cdc71e25a22..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { RoleMappingsGridPage } from '.'; -import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../../components'; -import { EmptyPrompt } from './empty_prompt'; -import { findTestSubject } from 'test_utils/find_test_subject'; -import { EuiLink } from '@elastic/eui'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; -import { act } from '@testing-library/react'; - -describe('RoleMappingsGridPage', () => { - it('renders an empty prompt when no role mappings exist', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(EmptyPrompt)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(EmptyPrompt)).toHaveLength(1); - }); - - it('renders a permission denied message when unauthorized to manage role mappings', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: false, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(PermissionDenied)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(PermissionDenied)).toHaveLength(1); - }); - - it('renders a warning when there are no compatible realms enabled', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - roles: [], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); - }); - - it('renders links to mapped roles', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - roles: ['superuser'], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); - expect(links).toHaveLength(1); - expect(links.at(0).props()).toMatchObject({ - href: '#/management/security/roles/edit/superuser', - }); - }); - - it('describes the number of mapped role templates', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - role_templates: [{}, {}], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - const templates = findTestSubject(wrapper, 'roleMappingRoles'); - expect(templates).toHaveLength(1); - expect(templates.text()).toEqual(`2 role templates defined`); - }); - - it('allows role mappings to be deleted, refreshing the grid after', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some-realm', - enabled: true, - roles: ['superuser'], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - deleteRoleMappings: jest.fn().mockReturnValue( - Promise.resolve([ - { - name: 'some-realm', - success: true, - }, - ]) - ), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(1); - expect(roleMappingsAPI.deleteRoleMappings).not.toHaveBeenCalled(); - - findTestSubject(wrapper, `deleteRoleMappingButton-some-realm`).simulate('click'); - expect(findTestSubject(wrapper, 'deleteRoleMappingConfirmationModal')).toHaveLength(1); - - await act(async () => { - findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); - await nextTick(); - wrapper.update(); - }); - - expect(roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['some-realm']); - // Expect an additional API call to refresh the grid - expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(2); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx deleted file mode 100644 index 9e925d0fa6dc0a..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; -// @ts-ignore -import template from './role_mappings.html'; -import { ROLE_MAPPINGS_PATH } from '../../management_urls'; -import { getRoleMappingBreadcrumbs } from '../../breadcrumbs'; -import { RoleMappingsGridPage } from './components'; - -routes.when(ROLE_MAPPINGS_PATH, { - template, - k7Breadcrumbs: getRoleMappingBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('roleMappingsGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html deleted file mode 100644 index cff3b821d132c0..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html deleted file mode 100644 index 0552b655afafd9..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js deleted file mode 100644 index e9c42824711b31..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from 'plugins/security/views/management/roles_grid/roles.html'; -import { ROLES_PATH } from '../management_urls'; -import { getRolesBreadcrumbs } from '../breadcrumbs'; -import { I18nContext } from 'ui/i18n'; -import { RolesGridPage } from './components'; - -routes.when(ROLES_PATH, { - template, - k7Breadcrumbs: getRolesBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('rolesGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html deleted file mode 100644 index 3dce7326d001ae..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js deleted file mode 100644 index 8d4e0526251d76..00000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from 'plugins/security/views/management/users_grid/users.html'; -import { SECURITY_PATH, USERS_PATH } from '../management_urls'; -import { UsersListPage } from './components'; -import { UserAPIClient } from '../../../lib/api'; -import { I18nContext } from 'ui/i18n'; -import { getUsersBreadcrumbs } from '../breadcrumbs'; - -routes.when(SECURITY_PATH, { - redirectTo: USERS_PATH, -}); - -const renderReact = (elem, changeUrl) => { - render( - - - , - elem - ); -}; - -routes.when(USERS_PATH, { - template, - k7Breadcrumbs: getUsersBreadcrumbs, - controller($scope, $http, kbnUrl) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('usersReactRoot'); - if (elem) unmountComponentAtNode(elem); - }); - $scope.$$postDigest(() => { - const elem = document.getElementById('usersReactRoot'); - const changeUrl = url => { - kbnUrl.change(url); - $scope.$apply(); - }; - renderReact(elem, $http, changeUrl); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index fb39c517e1c2ca..4c79c499cc0e6a 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -11,8 +11,7 @@ import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { npSetup } from 'ui/new_platform'; -import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; -import { AuthenticatedUser } from '../../../common/model'; +import { AuthenticatedUser, SecurityPluginSetup } from '../../../../../../plugins/security/public'; import { AuthenticationStatePage } from '../../components/authentication_state_page'; chrome diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index f3c65ed7e3cf17..121791d113bd5d 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -10,7 +10,16 @@ export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; export { BuiltinESPrivileges } from './builtin_es_privileges'; export { FeaturesPrivileges } from './features_privileges'; export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; -export { Role, RoleIndexPrivilege, RoleKibanaPrivilege } from './role'; +export { + Role, + RoleIndexPrivilege, + RoleKibanaPrivilege, + copyRole, + isReadOnlyRole, + isReservedRole, + isRoleEnabled, + prepareRoleClone, +} from './role'; export { KibanaPrivileges } from './kibana_privileges'; export { InlineRoleTemplate, diff --git a/x-pack/legacy/plugins/security/public/lib/role_utils.test.ts b/x-pack/plugins/security/common/model/role.test.ts similarity index 96% rename from x-pack/legacy/plugins/security/public/lib/role_utils.test.ts rename to x-pack/plugins/security/common/model/role.test.ts index 9d94017c3f0fe4..d4a910a1785ebd 100644 --- a/x-pack/legacy/plugins/security/public/lib/role_utils.test.ts +++ b/x-pack/plugins/security/common/model/role.test.ts @@ -4,14 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Role } from '../../common/model'; -import { - copyRole, - isReadOnlyRole, - isReservedRole, - isRoleEnabled, - prepareRoleClone, -} from './role_utils'; +import { Role, isReadOnlyRole, isReservedRole, isRoleEnabled, copyRole, prepareRoleClone } from '.'; describe('role', () => { describe('isRoleEnabled', () => { diff --git a/x-pack/plugins/security/common/model/role.ts b/x-pack/plugins/security/common/model/role.ts index 89f68aaa55b5cb..1edcf147262ede 100644 --- a/x-pack/plugins/security/common/model/role.ts +++ b/x-pack/plugins/security/common/model/role.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { cloneDeep } from 'lodash'; import { FeaturesPrivileges } from './features_privileges'; export interface RoleIndexPrivilege { @@ -40,3 +41,53 @@ export interface Role { _transform_error?: string[]; _unrecognized_applications?: string[]; } + +/** + * Returns whether given role is enabled or not + * + * @param role Object Role JSON, as returned by roles API + * @return Boolean true if role is enabled; false otherwise + */ +export function isRoleEnabled(role: Partial) { + return role.transient_metadata?.enabled ?? true; +} + +/** + * Returns whether given role is reserved or not. + * + * @param role Role as returned by roles API + */ +export function isReservedRole(role: Partial) { + return (role.metadata?._reserved as boolean) ?? false; +} + +/** + * Returns whether given role is editable through the UI or not. + * + * @param role the Role as returned by roles API + */ +export function isReadOnlyRole(role: Partial): boolean { + return isReservedRole(role) || (role._transform_error?.length ?? 0) > 0; +} + +/** + * Returns a deep copy of the role. + * + * @param role the Role to copy. + */ +export function copyRole(role: Role) { + return cloneDeep(role); +} + +/** + * Creates a deep copy of the role suitable for cloning. + * + * @param role the Role to clone. + */ +export function prepareRoleClone(role: Role): Role { + const clone = copyRole(role); + + clone.name = ''; + + return clone; +} diff --git a/x-pack/plugins/security/kibana.json b/x-pack/plugins/security/kibana.json index 32f860b1423d30..7d1940e393becf 100644 --- a/x-pack/plugins/security/kibana.json +++ b/x-pack/plugins/security/kibana.json @@ -3,7 +3,8 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "security"], - "requiredPlugins": ["features", "licensing"], + "requiredPlugins": ["data", "features", "licensing"], + "optionalPlugins": ["home", "management"], "server": true, "ui": true } diff --git a/x-pack/plugins/security/public/_index.scss b/x-pack/plugins/security/public/_index.scss new file mode 100644 index 00000000000000..9fa81bad7c3f40 --- /dev/null +++ b/x-pack/plugins/security/public/_index.scss @@ -0,0 +1,2 @@ +// Management styles +@import './management/index'; diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx similarity index 72% rename from x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx rename to x-pack/plugins/security/public/account_management/account_management_page.test.tsx index 366842e58e9e4a..b7cf8e6dd14181 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -6,11 +6,12 @@ import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { securityMock } from '../../../../../../../plugins/security/public/mocks'; +import { AuthenticatedUser } from '../../common/model'; import { AccountManagementPage } from './account_management_page'; -import { AuthenticatedUser } from '../../../../common/model'; -jest.mock('ui/kfetch'); +import { coreMock } from 'src/core/public/mocks'; +import { securityMock } from '../mocks'; +import { userAPIClientMock } from '../management/users/index.mock'; interface Options { withFullName?: boolean; @@ -45,7 +46,11 @@ describe('', () => { it(`displays users full name, username, and email address`, async () => { const user = createUser(); const wrapper = mountWithIntl( - + ); await act(async () => { @@ -63,7 +68,11 @@ describe('', () => { it(`displays username when full_name is not provided`, async () => { const user = createUser({ withFullName: false }); const wrapper = mountWithIntl( - + ); await act(async () => { @@ -77,7 +86,11 @@ describe('', () => { it(`displays a placeholder when no email address is provided`, async () => { const user = createUser({ withEmail: false }); const wrapper = mountWithIntl( - + ); await act(async () => { @@ -91,7 +104,11 @@ describe('', () => { it(`displays change password form for users in the native realm`, async () => { const user = createUser(); const wrapper = mountWithIntl( - + ); await act(async () => { @@ -106,7 +123,11 @@ describe('', () => { it(`does not display change password form for users in the saml realm`, async () => { const user = createUser({ realm: 'saml' }); const wrapper = mountWithIntl( - + ); await act(async () => { diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx similarity index 62% rename from x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx rename to x-pack/plugins/security/public/account_management/account_management_page.tsx index 6abee73e0b3535..3f764adc7949f2 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -5,20 +5,24 @@ */ import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; -import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; -import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model'; +import { NotificationsStart } from 'src/core/public'; +import { getUserDisplayName, AuthenticatedUser } from '../../common/model'; +import { AuthenticationServiceSetup } from '../authentication'; import { ChangePassword } from './change_password'; +import { UserAPIClient } from '../management'; import { PersonalInfo } from './personal_info'; interface Props { - securitySetup: SecurityPluginSetup; + authc: AuthenticationServiceSetup; + apiClient: PublicMethodsOf; + notifications: NotificationsStart; } -export const AccountManagementPage = (props: Props) => { +export const AccountManagementPage = ({ apiClient, authc, notifications }: Props) => { const [currentUser, setCurrentUser] = useState(null); useEffect(() => { - props.securitySetup.authc.getCurrentUser().then(setCurrentUser); - }, [props]); + authc.getCurrentUser().then(setCurrentUser); + }, [authc]); if (!currentUser) { return null; @@ -36,7 +40,7 @@ export const AccountManagementPage = (props: Props) => { - + diff --git a/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx b/x-pack/plugins/security/public/account_management/change_password/change_password.tsx similarity index 81% rename from x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx rename to x-pack/plugins/security/public/account_management/change_password/change_password.tsx index 63abb4539470d2..f5ac5f3b21d2e6 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx +++ b/x-pack/plugins/security/public/account_management/change_password/change_password.tsx @@ -3,18 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - // @ts-ignore - EuiDescribedFormGroup, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { UserAPIClient } from '../../../../lib/api'; -import { AuthenticatedUser, canUserChangePassword } from '../../../../../common/model'; -import { ChangePasswordForm } from '../../../../components/management/change_password_form'; +import { EuiDescribedFormGroup } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsSetup } from 'src/core/public'; +import { AuthenticatedUser, canUserChangePassword } from '../../../common/model'; +import { UserAPIClient } from '../../management/users'; +import { ChangePasswordForm } from '../../management/users/components/change_password_form'; interface Props { user: AuthenticatedUser; + apiClient: PublicMethodsOf; + notifications: NotificationsSetup; } export class ChangePassword extends Component { @@ -48,7 +48,8 @@ export class ChangePassword extends Component { ); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/change_password/index.ts b/x-pack/plugins/security/public/account_management/change_password/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/account/components/change_password/index.ts rename to x-pack/plugins/security/public/account_management/change_password/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/account/components/index.ts b/x-pack/plugins/security/public/account_management/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/account/components/index.ts rename to x-pack/plugins/security/public/account_management/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/account/components/personal_info/index.ts b/x-pack/plugins/security/public/account_management/personal_info/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/account/components/personal_info/index.ts rename to x-pack/plugins/security/public/account_management/personal_info/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/account/components/personal_info/personal_info.tsx b/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/account/components/personal_info/personal_info.tsx rename to x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx index 7121bf7ab28ee7..9cbbc242e8400e 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/personal_info/personal_info.tsx +++ b/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx @@ -3,15 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - // @ts-ignore - EuiDescribedFormGroup, - EuiFormRow, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { AuthenticatedUser } from '../../../../../common/model'; +import { EuiDescribedFormGroup, EuiFormRow, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AuthenticatedUser } from '../../../common/model'; interface Props { user: AuthenticatedUser; diff --git a/x-pack/plugins/security/public/management/_index.scss b/x-pack/plugins/security/public/management/_index.scss new file mode 100644 index 00000000000000..5d419b53230799 --- /dev/null +++ b/x-pack/plugins/security/public/management/_index.scss @@ -0,0 +1,3 @@ +@import './roles/index'; +@import './users/index'; +@import './role_mappings/index'; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts new file mode 100644 index 00000000000000..2a45d497029f41 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const apiKeysAPIClientMock = { + create: () => ({ + checkPrivileges: jest.fn(), + getApiKeys: jest.fn(), + invalidateApiKeys: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts new file mode 100644 index 00000000000000..7d51a80459a6e9 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { APIKeysAPIClient } from './api_keys_api_client'; + +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; + +describe('APIKeysAPIClient', () => { + it('checkPrivileges() queries correct endpoint', async () => { + const httpMock = httpServiceMock.createStartContract(); + + const mockResponse = Symbol('mockResponse'); + httpMock.get.mockResolvedValue(mockResponse); + + const apiClient = new APIKeysAPIClient(httpMock); + + await expect(apiClient.checkPrivileges()).resolves.toBe(mockResponse); + expect(httpMock.get).toHaveBeenCalledTimes(1); + expect(httpMock.get).toHaveBeenCalledWith('/internal/security/api_key/privileges'); + }); + + it('getApiKeys() queries correct endpoint', async () => { + const httpMock = httpServiceMock.createStartContract(); + + const mockResponse = Symbol('mockResponse'); + httpMock.get.mockResolvedValue(mockResponse); + + const apiClient = new APIKeysAPIClient(httpMock); + + await expect(apiClient.getApiKeys()).resolves.toBe(mockResponse); + expect(httpMock.get).toHaveBeenCalledTimes(1); + expect(httpMock.get).toHaveBeenCalledWith('/internal/security/api_key', { + query: { isAdmin: false }, + }); + httpMock.get.mockClear(); + + await expect(apiClient.getApiKeys(false)).resolves.toBe(mockResponse); + expect(httpMock.get).toHaveBeenCalledTimes(1); + expect(httpMock.get).toHaveBeenCalledWith('/internal/security/api_key', { + query: { isAdmin: false }, + }); + httpMock.get.mockClear(); + + await expect(apiClient.getApiKeys(true)).resolves.toBe(mockResponse); + expect(httpMock.get).toHaveBeenCalledTimes(1); + expect(httpMock.get).toHaveBeenCalledWith('/internal/security/api_key', { + query: { isAdmin: true }, + }); + }); + + it('invalidateApiKeys() queries correct endpoint', async () => { + const httpMock = httpServiceMock.createStartContract(); + + const mockResponse = Symbol('mockResponse'); + httpMock.post.mockResolvedValue(mockResponse); + + const apiClient = new APIKeysAPIClient(httpMock); + const mockAPIKeys = [ + { id: 'one', name: 'name-one' }, + { id: 'two', name: 'name-two' }, + ]; + + await expect(apiClient.invalidateApiKeys(mockAPIKeys)).resolves.toBe(mockResponse); + expect(httpMock.post).toHaveBeenCalledTimes(1); + expect(httpMock.post).toHaveBeenCalledWith('/internal/security/api_key/invalidate', { + body: JSON.stringify({ apiKeys: mockAPIKeys, isAdmin: false }), + }); + httpMock.post.mockClear(); + + await expect(apiClient.invalidateApiKeys(mockAPIKeys, false)).resolves.toBe(mockResponse); + expect(httpMock.post).toHaveBeenCalledTimes(1); + expect(httpMock.post).toHaveBeenCalledWith('/internal/security/api_key/invalidate', { + body: JSON.stringify({ apiKeys: mockAPIKeys, isAdmin: false }), + }); + httpMock.post.mockClear(); + + await expect(apiClient.invalidateApiKeys(mockAPIKeys, true)).resolves.toBe(mockResponse); + expect(httpMock.post).toHaveBeenCalledTimes(1); + expect(httpMock.post).toHaveBeenCalledWith('/internal/security/api_key/invalidate', { + body: JSON.stringify({ apiKeys: mockAPIKeys, isAdmin: true }), + }); + }); +}); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts new file mode 100644 index 00000000000000..372b1e56a73c47 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpStart } from 'src/core/public'; +import { ApiKey, ApiKeyToInvalidate } from '../../../common/model'; + +interface CheckPrivilegesResponse { + areApiKeysEnabled: boolean; + isAdmin: boolean; +} + +interface InvalidateApiKeysResponse { + itemsInvalidated: ApiKeyToInvalidate[]; + errors: any[]; +} + +interface GetApiKeysResponse { + apiKeys: ApiKey[]; +} + +const apiKeysUrl = '/internal/security/api_key'; + +export class APIKeysAPIClient { + constructor(private readonly http: HttpStart) {} + + public async checkPrivileges() { + return await this.http.get(`${apiKeysUrl}/privileges`); + } + + public async getApiKeys(isAdmin = false) { + return await this.http.get(apiKeysUrl, { query: { isAdmin } }); + } + + public async invalidateApiKeys(apiKeys: ApiKeyToInvalidate[], isAdmin = false) { + return await this.http.post(`${apiKeysUrl}/invalidate`, { + body: JSON.stringify({ apiKeys, isAdmin }), + }); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap similarity index 91% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/__snapshots__/api_keys_grid_page.test.tsx.snap rename to x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap index c2537235c99f61..42fd4417e238b1 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/__snapshots__/api_keys_grid_page.test.tsx.snap +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap @@ -1,7 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ApiKeysGridPage renders a callout when API keys are not enabled 1`] = ` - +exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = ` + Contact your system administrator and refer to the `; -exports[`ApiKeysGridPage renders permission denied if user does not have required permissions 1`] = ` +exports[`APIKeysGridPage renders permission denied if user does not have required permissions 1`] = ` ({ body: { statusCode: 403 } }); -const mock500 = () => ({ body: { error: 'Internal Server Error', message: '', statusCode: 500 } }); - -jest.mock('../../../../lib/api_keys_api', () => { - return { - ApiKeysApi: { - async checkPrivileges() { - if (mockSimulate403) { - throw mock403(); - } - - return { - isAdmin: mockIsAdmin, - areApiKeysEnabled: mockAreApiKeysEnabled, - }; - }, - async getApiKeys() { - if (mockSimulate500) { - throw mock500(); - } - - return { - apiKeys: [ - { - creation: 1571322182082, - expiration: 1571408582082, - id: '0QQZ2m0BO2XZwgJFuWTT', - invalidated: false, - name: 'my-api-key', - realm: 'reserved', - username: 'elastic', - }, - ], - }; - }, - }, - }; -}); - import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { ApiKeysGridPage } from './api_keys_grid_page'; import React from 'react'; import { ReactWrapper } from 'enzyme'; import { EuiCallOut } from '@elastic/eui'; import { NotEnabled } from './not_enabled'; import { PermissionDenied } from './permission_denied'; +import { APIKeysAPIClient } from '../api_keys_api_client'; +import { DocumentationLinksService } from '../documentation_links'; +import { APIKeysGridPage } from './api_keys_grid_page'; + +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { apiKeysAPIClientMock } from '../index.mock'; + +const mock403 = () => ({ body: { statusCode: 403 } }); +const mock500 = () => ({ body: { error: 'Internal Server Error', message: '', statusCode: 500 } }); const waitForRender = async ( wrapper: ReactWrapper, @@ -77,23 +41,51 @@ const waitForRender = async ( }); }; -describe('ApiKeysGridPage', () => { +describe('APIKeysGridPage', () => { + let apiClientMock: jest.Mocked>; beforeEach(() => { - mockSimulate403 = false; - mockSimulate500 = false; - mockAreApiKeysEnabled = true; - mockIsAdmin = true; + apiClientMock = apiKeysAPIClientMock.create(); + apiClientMock.checkPrivileges.mockResolvedValue({ + isAdmin: true, + areApiKeysEnabled: true, + }); + apiClientMock.getApiKeys.mockResolvedValue({ + apiKeys: [ + { + creation: 1571322182082, + expiration: 1571408582082, + id: '0QQZ2m0BO2XZwgJFuWTT', + invalidated: false, + name: 'my-api-key', + realm: 'reserved', + username: 'elastic', + }, + ], + }); }); + const getViewProperties = () => { + const { docLinks, notifications } = coreMock.createStart(); + return { + docLinks: new DocumentationLinksService(docLinks), + notifications, + apiKeysAPIClient: apiClientMock, + }; + }; + it('renders a loading state when fetching API keys', async () => { - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); expect(wrapper.find('[data-test-subj="apiKeysSectionLoading"]')).toHaveLength(1); }); it('renders a callout when API keys are not enabled', async () => { - mockAreApiKeysEnabled = false; - const wrapper = mountWithIntl(); + apiClientMock.checkPrivileges.mockResolvedValue({ + isAdmin: true, + areApiKeysEnabled: false, + }); + + const wrapper = mountWithIntl(); await waitForRender(wrapper, updatedWrapper => { return updatedWrapper.find(NotEnabled).length > 0; @@ -103,8 +95,9 @@ describe('ApiKeysGridPage', () => { }); it('renders permission denied if user does not have required permissions', async () => { - mockSimulate403 = true; - const wrapper = mountWithIntl(); + apiClientMock.checkPrivileges.mockRejectedValue(mock403()); + + const wrapper = mountWithIntl(); await waitForRender(wrapper, updatedWrapper => { return updatedWrapper.find(PermissionDenied).length > 0; @@ -114,8 +107,9 @@ describe('ApiKeysGridPage', () => { }); it('renders error callout if error fetching API keys', async () => { - mockSimulate500 = true; - const wrapper = mountWithIntl(); + apiClientMock.getApiKeys.mockRejectedValue(mock500()); + + const wrapper = mountWithIntl(); await waitForRender(wrapper, updatedWrapper => { return updatedWrapper.find(EuiCallOut).length > 0; @@ -125,7 +119,10 @@ describe('ApiKeysGridPage', () => { }); describe('Admin view', () => { - const wrapper = mountWithIntl(); + let wrapper: ReactWrapper; + beforeEach(() => { + wrapper = mountWithIntl(); + }); it('renders a callout indicating the user is an administrator', async () => { const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]'; @@ -151,8 +148,15 @@ describe('ApiKeysGridPage', () => { }); describe('Non-admin view', () => { - mockIsAdmin = false; - const wrapper = mountWithIntl(); + let wrapper: ReactWrapper; + beforeEach(() => { + apiClientMock.checkPrivileges.mockResolvedValue({ + isAdmin: false, + areApiKeysEnabled: true, + }); + + wrapper = mountWithIntl(); + }); it('does NOT render a callout indicating the user is an administrator', async () => { const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx similarity index 90% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx rename to x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 92633a4b0ef57c..779a2302cfadf9 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -27,16 +27,23 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment-timezone'; import _ from 'lodash'; -import { toastNotifications } from 'ui/notify'; +import { NotificationsStart } from 'src/core/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SectionLoading } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/section_loading'; -import { ApiKey, ApiKeyToInvalidate } from '../../../../../common/model'; -import { ApiKeysApi } from '../../../../lib/api_keys_api'; +import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public/components/section_loading'; +import { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; +import { APIKeysAPIClient } from '../api_keys_api_client'; +import { DocumentationLinksService } from '../documentation_links'; import { PermissionDenied } from './permission_denied'; import { EmptyPrompt } from './empty_prompt'; import { NotEnabled } from './not_enabled'; import { InvalidateProvider } from './invalidate_provider'; +interface Props { + notifications: NotificationsStart; + docLinks: DocumentationLinksService; + apiKeysAPIClient: PublicMethodsOf; +} + interface State { isLoadingApp: boolean; isLoadingTable: boolean; @@ -50,7 +57,7 @@ interface State { const DATE_FORMAT = 'MMMM Do YYYY HH:mm:ss'; -export class ApiKeysGridPage extends Component { +export class APIKeysGridPage extends Component { constructor(props: any) { super(props); this.state = { @@ -124,7 +131,7 @@ export class ApiKeysGridPage extends Component { if (!areApiKeysEnabled) { return ( - + ); } @@ -132,7 +139,7 @@ export class ApiKeysGridPage extends Component { if (!isLoadingTable && apiKeys && apiKeys.length === 0) { return ( - + ); } @@ -210,7 +217,11 @@ export class ApiKeysGridPage extends Component { const search: EuiInMemoryTableProps['search'] = { toolsLeft: selectedItems.length ? ( - + {invalidateApiKeyPrompt => { return ( { return ( - + {invalidateApiKeyPrompt => { return ( { private async checkPrivileges() { try { - const { isAdmin, areApiKeysEnabled } = await ApiKeysApi.checkPrivileges(); + const { isAdmin, areApiKeysEnabled } = await this.props.apiKeysAPIClient.checkPrivileges(); this.setState({ isAdmin, areApiKeysEnabled }); if (areApiKeysEnabled) { @@ -494,14 +509,11 @@ export class ApiKeysGridPage extends Component { if (_.get(e, 'body.statusCode') === 403) { this.setState({ permissionDenied: true, isLoadingApp: false }); } else { - toastNotifications.addDanger( - this.props.i18n.translate( - 'xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage', - { - defaultMessage: 'Error checking privileges: {message}', - values: { message: _.get(e, 'body.message', '') }, - } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage', { + defaultMessage: 'Error checking privileges: {message}', + values: { message: _.get(e, 'body.message', '') }, + }) ); } } @@ -520,7 +532,7 @@ export class ApiKeysGridPage extends Component { private loadApiKeys = async () => { try { const { isAdmin } = this.state; - const { apiKeys } = await ApiKeysApi.getApiKeys(isAdmin); + const { apiKeys } = await this.props.apiKeysAPIClient.getApiKeys(isAdmin); this.setState({ apiKeys }); } catch (e) { this.setState({ error: e }); diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/empty_prompt/empty_prompt.tsx rename to x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx index 957ca7010a1a0a..7d762a1ceb04e0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx @@ -7,13 +7,14 @@ import React, { Fragment } from 'react'; import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { documentationLinks } from '../../services/documentation_links'; +import { DocumentationLinksService } from '../../documentation_links'; interface Props { isAdmin: boolean; + docLinks: DocumentationLinksService; } -export const EmptyPrompt: React.FunctionComponent = ({ isAdmin }) => ( +export const EmptyPrompt: React.FunctionComponent = ({ isAdmin, docLinks }) => ( = ({ isAdmin }) => ( defaultMessage="You can create an {link} from Console." values={{ link: ( - + React.ReactElement; + notifications: NotificationsStart; + apiKeysAPIClient: PublicMethodsOf; } export type InvalidateApiKeys = ( @@ -23,7 +25,12 @@ export type InvalidateApiKeys = ( type OnSuccessCallback = (apiKeysInvalidated: ApiKeyToInvalidate[]) => void; -export const InvalidateProvider: React.FunctionComponent = ({ isAdmin, children }) => { +export const InvalidateProvider: React.FunctionComponent = ({ + isAdmin, + children, + notifications, + apiKeysAPIClient, +}) => { const [apiKeys, setApiKeys] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const onSuccessCallback = useRef(null); @@ -48,7 +55,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ isAdmin, ch let errors; try { - result = await ApiKeysApi.invalidateApiKeys(apiKeys, isAdmin); + result = await apiKeysAPIClient.invalidateApiKeys(apiKeys, isAdmin); } catch (e) { error = e; } @@ -77,7 +84,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ isAdmin, ch values: { name: itemsInvalidated[0].name }, } ); - toastNotifications.addSuccess(successMessage); + notifications.toasts.addSuccess(successMessage); if (onSuccessCallback.current) { onSuccessCallback.current([...itemsInvalidated]); } @@ -106,7 +113,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ isAdmin, ch values: { name: (errors && errors[0].name) || apiKeys[0].name }, } ); - toastNotifications.addDanger(errorMessage); + notifications.toasts.addDanger(errorMessage); } }; diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/not_enabled/index.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/not_enabled/index.ts rename to x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/not_enabled/not_enabled.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx similarity index 78% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/not_enabled/not_enabled.tsx rename to x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx index c419e15450c1e1..08fe5425577573 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/not_enabled/not_enabled.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx @@ -7,9 +7,13 @@ import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { documentationLinks } from '../../services/documentation_links'; +import { DocumentationLinksService } from '../../documentation_links'; -export const NotEnabled: React.FunctionComponent = () => ( +interface Props { + docLinks: DocumentationLinksService; +} + +export const NotEnabled: React.FunctionComponent = ({ docLinks }) => ( ( defaultMessage="Contact your system administrator and refer to the {link} to enable API keys." values={{ link: ( - + ({ + APIKeysGridPage: (props: any) => `Page: ${JSON.stringify(props)}`, +})); + +import { apiKeysManagementApp } from './api_keys_management_app'; +import { coreMock } from '../../../../../../src/core/public/mocks'; + +describe('apiKeysManagementApp', () => { + it('create() returns proper management app descriptor', () => { + const { getStartServices } = coreMock.createSetup(); + + expect(apiKeysManagementApp.create({ getStartServices: getStartServices as any })) + .toMatchInlineSnapshot(` + Object { + "id": "api_keys", + "mount": [Function], + "order": 30, + "title": "API Keys", + } + `); + }); + + it('mount() works for the `grid` page', async () => { + const { getStartServices } = coreMock.createSetup(); + const container = document.createElement('div'); + + const setBreadcrumbs = jest.fn(); + const unmount = await apiKeysManagementApp + .create({ getStartServices: getStartServices as any }) + .mount({ + basePath: '/some-base-path', + element: container, + setBreadcrumbs, + }); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '#/some-base-path', text: 'API Keys' }]); + expect(container).toMatchInlineSnapshot(` +
+ Page: {"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"apiKeysAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); +}); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx new file mode 100644 index 00000000000000..35de732b84ce9b --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup } from 'src/core/public'; +import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import { PluginStartDependencies } from '../../plugin'; +import { APIKeysGridPage } from './api_keys_grid'; +import { APIKeysAPIClient } from './api_keys_api_client'; +import { DocumentationLinksService } from './documentation_links'; + +interface CreateParams { + getStartServices: CoreSetup['getStartServices']; +} + +export const apiKeysManagementApp = Object.freeze({ + id: 'api_keys', + create({ getStartServices }: CreateParams) { + return { + id: this.id, + order: 30, + title: i18n.translate('xpack.security.management.apiKeysTitle', { + defaultMessage: 'API Keys', + }), + async mount({ basePath, element, setBreadcrumbs }) { + const [{ docLinks, http, notifications, i18n: i18nStart }] = await getStartServices(); + setBreadcrumbs([ + { + text: i18n.translate('xpack.security.apiKeys.breadcrumb', { + defaultMessage: 'API Keys', + }), + href: `#${basePath}`, + }, + ]); + + render( + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; + }, + } as RegisterManagementAppArgs; + }, +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/services/documentation_links.ts b/x-pack/plugins/security/public/management/api_keys/documentation_links.ts similarity index 51% rename from x-pack/legacy/plugins/security/public/views/management/api_keys_grid/services/documentation_links.ts rename to x-pack/plugins/security/public/management/api_keys/documentation_links.ts index 1f03763eb542a3..4165c2a2372c96 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/services/documentation_links.ts +++ b/x-pack/plugins/security/public/management/api_keys/documentation_links.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { DocLinksStart } from 'src/core/public'; -class DocumentationLinksService { - private esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`; +export class DocumentationLinksService { + private readonly esDocBasePath: string; - public getApiKeyServiceSettingsDocUrl(): string { + constructor(docLinks: DocLinksStart) { + this.esDocBasePath = `${docLinks.ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${docLinks.DOC_LINK_VERSION}/`; + } + + public getApiKeyServiceSettingsDocUrl() { return `${this.esDocBasePath}security-settings.html#api-key-service-settings`; } - public getCreateApiKeyDocUrl(): string { + public getCreateApiKeyDocUrl() { return `${this.esDocBasePath}security-api-create-api-key.html`; } } - -export const documentationLinks = new DocumentationLinksService(); diff --git a/x-pack/plugins/security/public/management/api_keys/index.mock.ts b/x-pack/plugins/security/public/management/api_keys/index.mock.ts new file mode 100644 index 00000000000000..3c11cd6bb9c654 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/index.mock.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { apiKeysAPIClientMock } from './api_keys_api_client.mock'; diff --git a/x-pack/plugins/security/public/management/api_keys/index.ts b/x-pack/plugins/security/public/management/api_keys/index.ts new file mode 100644 index 00000000000000..e15da7d5eb4092 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { apiKeysManagementApp } from './api_keys_management_app'; diff --git a/x-pack/plugins/security/public/management/index.ts b/x-pack/plugins/security/public/management/index.ts new file mode 100644 index 00000000000000..e1a13d66e68836 --- /dev/null +++ b/x-pack/plugins/security/public/management/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ManagementService } from './management_service'; +export { UserAPIClient } from './users/user_api_client'; diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts new file mode 100644 index 00000000000000..53c12ad7ab12c4 --- /dev/null +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BehaviorSubject } from 'rxjs'; +import { ManagementApp } from '../../../../../src/plugins/management/public'; +import { SecurityLicenseFeatures } from '../../common/licensing/license_features'; +import { ManagementService } from './management_service'; +import { usersManagementApp } from './users'; + +import { coreMock } from '../../../../../src/core/public/mocks'; +import { licenseMock } from '../../common/licensing/index.mock'; +import { securityMock } from '../mocks'; +import { rolesManagementApp } from './roles'; +import { apiKeysManagementApp } from './api_keys'; +import { roleMappingsManagementApp } from './role_mappings'; + +describe('ManagementService', () => { + describe('setup()', () => { + it('properly registers security section and its applications', () => { + const { fatalErrors, getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); + const license = licenseMock.create(); + + const mockSection = { registerApp: jest.fn() }; + const managementSetup = { + sections: { + getSection: jest.fn(), + getAllSections: jest.fn(), + register: jest.fn().mockReturnValue(mockSection), + }, + }; + + const service = new ManagementService(); + service.setup({ + getStartServices: getStartServices as any, + license, + fatalErrors, + authc, + management: managementSetup, + }); + + expect(managementSetup.sections.register).toHaveBeenCalledTimes(1); + expect(managementSetup.sections.register).toHaveBeenCalledWith({ + id: 'security', + title: 'Security', + order: 100, + euiIconType: 'securityApp', + }); + + expect(mockSection.registerApp).toHaveBeenCalledTimes(4); + expect(mockSection.registerApp).toHaveBeenCalledWith({ + id: 'users', + mount: expect.any(Function), + order: 10, + title: 'Users', + }); + expect(mockSection.registerApp).toHaveBeenCalledWith({ + id: 'roles', + mount: expect.any(Function), + order: 20, + title: 'Roles', + }); + expect(mockSection.registerApp).toHaveBeenCalledWith({ + id: 'api_keys', + mount: expect.any(Function), + order: 30, + title: 'API Keys', + }); + expect(mockSection.registerApp).toHaveBeenCalledWith({ + id: 'role_mappings', + mount: expect.any(Function), + order: 40, + title: 'Role Mappings', + }); + }); + }); + + describe('start()', () => { + function startService(initialFeatures: Partial) { + const { fatalErrors, getStartServices } = coreMock.createSetup(); + + const licenseSubject = new BehaviorSubject( + (initialFeatures as unknown) as SecurityLicenseFeatures + ); + const license = licenseMock.create(); + license.features$ = licenseSubject; + + const service = new ManagementService(); + service.setup({ + getStartServices: getStartServices as any, + license, + fatalErrors, + authc: securityMock.createSetup().authc, + management: { + sections: { + getSection: jest.fn(), + getAllSections: jest.fn(), + register: jest.fn().mockReturnValue({ registerApp: jest.fn() }), + }, + }, + }); + + const getMockedApp = () => { + // All apps are enabled by default. + let enabled = true; + return ({ + get enabled() { + return enabled; + }, + enable: jest.fn().mockImplementation(() => { + enabled = true; + }), + disable: jest.fn().mockImplementation(() => { + enabled = false; + }), + } as unknown) as jest.Mocked; + }; + const mockApps = new Map>([ + [usersManagementApp.id, getMockedApp()], + [rolesManagementApp.id, getMockedApp()], + [apiKeysManagementApp.id, getMockedApp()], + [roleMappingsManagementApp.id, getMockedApp()], + ] as Array<[string, jest.Mocked]>); + + service.start({ + management: { + sections: { + getSection: jest + .fn() + .mockReturnValue({ getApp: jest.fn().mockImplementation(id => mockApps.get(id)) }), + getAllSections: jest.fn(), + navigateToApp: jest.fn(), + }, + legacy: undefined, + }, + }); + + return { + mockApps, + updateFeatures(features: Partial) { + licenseSubject.next((features as unknown) as SecurityLicenseFeatures); + }, + }; + } + + it('does not do anything if `showLinks` is `true` at `start`', () => { + const { mockApps } = startService({ showLinks: true, showRoleMappingsManagement: true }); + for (const [, mockApp] of mockApps) { + expect(mockApp.enable).not.toHaveBeenCalled(); + expect(mockApp.disable).not.toHaveBeenCalled(); + expect(mockApp.enabled).toBe(true); + } + }); + + it('disables all apps if `showLinks` is `false` at `start`', () => { + const { mockApps } = startService({ showLinks: false, showRoleMappingsManagement: true }); + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(false); + } + }); + + it('disables only Role Mappings app if `showLinks` is `true`, but `showRoleMappingsManagement` is `false` at `start`', () => { + const { mockApps } = startService({ showLinks: true, showRoleMappingsManagement: false }); + for (const [appId, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(appId !== roleMappingsManagementApp.id); + } + }); + + it('apps are disabled if `showLinks` changes after `start`', () => { + const { mockApps, updateFeatures } = startService({ + showLinks: true, + showRoleMappingsManagement: true, + }); + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(true); + } + + updateFeatures({ showLinks: false, showRoleMappingsManagement: false }); + + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(false); + } + }); + + it('role mappings app is disabled if `showRoleMappingsManagement` changes after `start`', () => { + const { mockApps, updateFeatures } = startService({ + showLinks: true, + showRoleMappingsManagement: true, + }); + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(true); + } + + updateFeatures({ showLinks: true, showRoleMappingsManagement: false }); + + for (const [appId, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(appId !== roleMappingsManagementApp.id); + } + }); + + it('apps are re-enabled if `showLinks` eventually transitions to `true` after `start`', () => { + const { mockApps, updateFeatures } = startService({ + showLinks: true, + showRoleMappingsManagement: true, + }); + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(true); + } + + updateFeatures({ showLinks: false, showRoleMappingsManagement: false }); + + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(false); + } + + updateFeatures({ showLinks: true, showRoleMappingsManagement: true }); + + for (const [, mockApp] of mockApps) { + expect(mockApp.enabled).toBe(true); + } + }); + }); +}); diff --git a/x-pack/plugins/security/public/management/management_service.ts b/x-pack/plugins/security/public/management/management_service.ts new file mode 100644 index 00000000000000..5ad3681590fbf6 --- /dev/null +++ b/x-pack/plugins/security/public/management/management_service.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Subscription } from 'rxjs'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, FatalErrorsSetup } from 'src/core/public'; +import { + ManagementApp, + ManagementSetup, + ManagementStart, +} from '../../../../../src/plugins/management/public'; +import { SecurityLicense } from '../../common/licensing'; +import { AuthenticationServiceSetup } from '../authentication'; +import { PluginStartDependencies } from '../plugin'; +import { apiKeysManagementApp } from './api_keys'; +import { roleMappingsManagementApp } from './role_mappings'; +import { rolesManagementApp } from './roles'; +import { usersManagementApp } from './users'; + +interface SetupParams { + management: ManagementSetup; + license: SecurityLicense; + authc: AuthenticationServiceSetup; + fatalErrors: FatalErrorsSetup; + getStartServices: CoreSetup['getStartServices']; +} + +interface StartParams { + management: ManagementStart; +} + +export class ManagementService { + private license!: SecurityLicense; + private licenseFeaturesSubscription?: Subscription; + + setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) { + this.license = license; + + const securitySection = management.sections.register({ + id: 'security', + title: i18n.translate('xpack.security.management.securityTitle', { + defaultMessage: 'Security', + }), + order: 100, + euiIconType: 'securityApp', + }); + + securitySection.registerApp(usersManagementApp.create({ authc, getStartServices })); + securitySection.registerApp( + rolesManagementApp.create({ fatalErrors, license, getStartServices }) + ); + securitySection.registerApp(apiKeysManagementApp.create({ getStartServices })); + securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices })); + } + + start({ management }: StartParams) { + this.licenseFeaturesSubscription = this.license.features$.subscribe(async features => { + const securitySection = management.sections.getSection('security')!; + + const securityManagementAppsStatuses: Array<[ManagementApp, boolean]> = [ + [securitySection.getApp(usersManagementApp.id)!, features.showLinks], + [securitySection.getApp(rolesManagementApp.id)!, features.showLinks], + [securitySection.getApp(apiKeysManagementApp.id)!, features.showLinks], + [ + securitySection.getApp(roleMappingsManagementApp.id)!, + features.showLinks && features.showRoleMappingsManagement, + ], + ]; + + // Iterate over all registered apps and update their enable status depending on the available + // license features. + for (const [app, enableStatus] of securityManagementAppsStatuses) { + if (app.enabled === enableStatus) { + continue; + } + + if (enableStatus) { + app.enable(); + } else { + app.disable(); + } + } + }); + } + + stop() { + if (this.licenseFeaturesSubscription) { + this.licenseFeaturesSubscription.unsubscribe(); + this.licenseFeaturesSubscription = undefined; + } + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/management_urls.ts b/x-pack/plugins/security/public/management/management_urls.ts similarity index 74% rename from x-pack/legacy/plugins/security/public/views/management/management_urls.ts rename to x-pack/plugins/security/public/management/management_urls.ts index 881740c0b2895b..0d4e3fc920bdba 100644 --- a/x-pack/legacy/plugins/security/public/views/management/management_urls.ts +++ b/x-pack/plugins/security/public/management/management_urls.ts @@ -4,19 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -export const MANAGEMENT_PATH = '/management'; -export const SECURITY_PATH = `${MANAGEMENT_PATH}/security`; +const MANAGEMENT_PATH = '/management'; +const SECURITY_PATH = `${MANAGEMENT_PATH}/security`; export const ROLES_PATH = `${SECURITY_PATH}/roles`; export const EDIT_ROLES_PATH = `${ROLES_PATH}/edit`; export const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; export const USERS_PATH = `${SECURITY_PATH}/users`; export const EDIT_USERS_PATH = `${USERS_PATH}/edit`; -export const API_KEYS_PATH = `${SECURITY_PATH}/api_keys`; export const ROLE_MAPPINGS_PATH = `${SECURITY_PATH}/role_mappings`; -export const CREATE_ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/edit`; +const CREATE_ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/edit`; export const getEditRoleHref = (roleName: string) => - `#${EDIT_ROLES_PATH}/${encodeURIComponent(roleName)}`; + `#${ROLES_PATH}/edit/${encodeURIComponent(roleName)}`; export const getCreateRoleMappingHref = () => `#${CREATE_ROLE_MAPPING_PATH}`; diff --git a/x-pack/plugins/security/public/management/role_mappings/_index.scss b/x-pack/plugins/security/public/management/role_mappings/_index.scss new file mode 100644 index 00000000000000..bae6effcd2ec56 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/_index.scss @@ -0,0 +1 @@ +@import './edit_role_mapping/index'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx similarity index 64% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx index b826d68053e276..69142b1ad610e6 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx @@ -5,24 +5,15 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { DeleteProvider } from '.'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; -import { RoleMapping } from '../../../../../../common/model'; import { EuiConfirmModal } from '@elastic/eui'; -import { findTestSubject } from 'test_utils/find_test_subject'; import { act } from '@testing-library/react'; -import { toastNotifications } from 'ui/notify'; - -jest.mock('ui/notify', () => { - return { - toastNotifications: { - addError: jest.fn(), - addSuccess: jest.fn(), - addDanger: jest.fn(), - }, - }; -}); +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { RoleMapping } from '../../../../../common/model'; +import { DeleteProvider } from '.'; + +import { roleMappingsAPIClientMock } from '../../index.mock'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; describe('DeleteProvider', () => { beforeEach(() => { @@ -30,17 +21,14 @@ describe('DeleteProvider', () => { }); it('allows a single role mapping to be deleted', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.deleteRoleMappings.mockResolvedValue([{ name: 'delete-me', success: true }]); + + const notifications = coreMock.createStart().notifications; + const props = { - roleMappingsAPI: ({ - deleteRoleMappings: jest.fn().mockReturnValue( - Promise.resolve([ - { - name: 'delete-me', - success: true, - }, - ]) - ), - } as unknown) as RoleMappingsAPI, + roleMappingsAPI, + notifications, }; const roleMappingsToDelete = [ @@ -79,11 +67,10 @@ describe('DeleteProvider', () => { expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['delete-me']); - const notifications = toastNotifications as jest.Mocked; - expect(notifications.addError).toHaveBeenCalledTimes(0); - expect(notifications.addDanger).toHaveBeenCalledTimes(0); - expect(notifications.addSuccess).toHaveBeenCalledTimes(1); - expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + expect(notifications.toasts.addError).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.toasts.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "data-test-subj": "deletedRoleMappingSuccessToast", @@ -94,21 +81,23 @@ describe('DeleteProvider', () => { }); it('allows multiple role mappings to be deleted', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.deleteRoleMappings.mockResolvedValue([ + { + name: 'delete-me', + success: true, + }, + { + name: 'delete-me-too', + success: true, + }, + ]); + + const notifications = coreMock.createStart().notifications; + const props = { - roleMappingsAPI: ({ - deleteRoleMappings: jest.fn().mockReturnValue( - Promise.resolve([ - { - name: 'delete-me', - success: true, - }, - { - name: 'delete-me-too', - success: true, - }, - ]) - ), - } as unknown) as RoleMappingsAPI, + roleMappingsAPI, + notifications, }; const roleMappingsToDelete = [ @@ -152,11 +141,11 @@ describe('DeleteProvider', () => { 'delete-me', 'delete-me-too', ]); - const notifications = toastNotifications as jest.Mocked; - expect(notifications.addError).toHaveBeenCalledTimes(0); - expect(notifications.addDanger).toHaveBeenCalledTimes(0); - expect(notifications.addSuccess).toHaveBeenCalledTimes(1); - expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + + expect(notifications.toasts.addError).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.toasts.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "data-test-subj": "deletedRoleMappingSuccessToast", @@ -167,22 +156,24 @@ describe('DeleteProvider', () => { }); it('handles mixed success/failure conditions', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.deleteRoleMappings.mockResolvedValue([ + { + name: 'delete-me', + success: true, + }, + { + name: 'i-wont-work', + success: false, + error: new Error('something went wrong. sad.'), + }, + ]); + + const notifications = coreMock.createStart().notifications; + const props = { - roleMappingsAPI: ({ - deleteRoleMappings: jest.fn().mockReturnValue( - Promise.resolve([ - { - name: 'delete-me', - success: true, - }, - { - name: 'i-wont-work', - success: false, - error: new Error('something went wrong. sad.'), - }, - ]) - ), - } as unknown) as RoleMappingsAPI, + roleMappingsAPI, + notifications, }; const roleMappingsToDelete = [ @@ -223,10 +214,9 @@ describe('DeleteProvider', () => { 'i-wont-work', ]); - const notifications = toastNotifications as jest.Mocked; - expect(notifications.addError).toHaveBeenCalledTimes(0); - expect(notifications.addSuccess).toHaveBeenCalledTimes(1); - expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + expect(notifications.toasts.addError).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.toasts.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "data-test-subj": "deletedRoleMappingSuccessToast", @@ -235,8 +225,8 @@ describe('DeleteProvider', () => { ] `); - expect(notifications.addDanger).toHaveBeenCalledTimes(1); - expect(notifications.addDanger.mock.calls[0]).toMatchInlineSnapshot(` + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1); + expect(notifications.toasts.addDanger.mock.calls[0]).toMatchInlineSnapshot(` Array [ "Error deleting role mapping 'i-wont-work'", ] @@ -244,12 +234,13 @@ describe('DeleteProvider', () => { }); it('handles errors calling the API', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.deleteRoleMappings.mockRejectedValue(new Error('AHHHHH')); + + const notifications = coreMock.createStart().notifications; const props = { - roleMappingsAPI: ({ - deleteRoleMappings: jest.fn().mockImplementation(() => { - throw new Error('AHHHHH'); - }), - } as unknown) as RoleMappingsAPI, + roleMappingsAPI, + notifications, }; const roleMappingsToDelete = [ @@ -284,12 +275,11 @@ describe('DeleteProvider', () => { expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['delete-me']); - const notifications = toastNotifications as jest.Mocked; - expect(notifications.addDanger).toHaveBeenCalledTimes(0); - expect(notifications.addSuccess).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(0); - expect(notifications.addError).toHaveBeenCalledTimes(1); - expect(notifications.addError.mock.calls[0]).toMatchInlineSnapshot(` + expect(notifications.toasts.addError).toHaveBeenCalledTimes(1); + expect(notifications.toasts.addError.mock.calls[0]).toMatchInlineSnapshot(` Array [ [Error: AHHHHH], Object { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx similarity index 93% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx rename to x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx index 2072cedeab4628..860fe22cb8032c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx @@ -6,13 +6,14 @@ import React, { Fragment, useRef, useState, ReactElement } from 'react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { RoleMapping } from '../../../../../../common/model'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { NotificationsStart } from 'src/core/public'; +import { RoleMapping } from '../../../../../common/model'; +import { RoleMappingsAPIClient } from '../../role_mappings_api_client'; interface Props { - roleMappingsAPI: RoleMappingsAPI; + roleMappingsAPI: PublicMethodsOf; + notifications: NotificationsStart; children: (deleteMappings: DeleteRoleMappings) => ReactElement; } @@ -23,7 +24,11 @@ export type DeleteRoleMappings = ( type OnSuccessCallback = (deletedRoleMappings: string[]) => void; -export const DeleteProvider: React.FunctionComponent = ({ roleMappingsAPI, children }) => { +export const DeleteProvider: React.FunctionComponent = ({ + roleMappingsAPI, + children, + notifications, +}) => { const [roleMappings, setRoleMappings] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteInProgress, setIsDeleteInProgress] = useState(false); @@ -55,7 +60,7 @@ export const DeleteProvider: React.FunctionComponent = ({ roleMappingsAPI try { result = await roleMappingsAPI.deleteRoleMappings(roleMappings.map(rm => rm.name)); } catch (e) { - toastNotifications.addError(e, { + notifications.toasts.addError(e, { title: i18n.translate( 'xpack.security.management.roleMappings.deleteRoleMapping.unknownError', { @@ -92,7 +97,7 @@ export const DeleteProvider: React.FunctionComponent = ({ roleMappingsAPI values: { name: successfulDeletes[0].name }, } ); - toastNotifications.addSuccess({ + notifications.toasts.addSuccess({ title: successMessage, 'data-test-subj': 'deletedRoleMappingSuccessToast', }); @@ -121,7 +126,7 @@ export const DeleteProvider: React.FunctionComponent = ({ roleMappingsAPI values: { name: erroredDeletes[0].name }, } ); - toastNotifications.addDanger(errorMessage); + notifications.toasts.addDanger(errorMessage); } }; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/index.ts b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/index.ts rename to x-pack/plugins/security/public/management/role_mappings/components/delete_provider/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/index.ts b/x-pack/plugins/security/public/management/role_mappings/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/index.ts rename to x-pack/plugins/security/public/management/role_mappings/components/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/index.ts b/x-pack/plugins/security/public/management/role_mappings/components/no_compatible_realms/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/index.ts rename to x-pack/plugins/security/public/management/role_mappings/components/no_compatible_realms/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx b/x-pack/plugins/security/public/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx similarity index 78% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx rename to x-pack/plugins/security/public/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx index 969832b3ecbae1..5e14b0c179bfd7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx @@ -7,9 +7,13 @@ import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { documentationLinks } from '../../services/documentation_links'; +import { DocumentationLinksService } from '../../documentation_links'; -export const NoCompatibleRealms: React.FunctionComponent = () => ( +interface Props { + docLinks: DocumentationLinksService; +} + +export const NoCompatibleRealms: React.FunctionComponent = ({ docLinks }: Props) => ( ( defaultMessage="Role mappings will not be applied to any users. Contact your system administrator and refer to the {link} for more information." values={{ link: ( - + { + let rolesAPI: PublicMethodsOf; + beforeEach(() => { + rolesAPI = rolesAPIClientMock.create(); + (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ + { name: 'foo_role' }, + { name: 'bar role' }, + ] as Role[]); + }); + + it('allows a role mapping to be created', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.saveRoleMapping.mockResolvedValue(null); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + findTestSubject(wrapper, 'roleMappingFormNameInput').simulate('change', { + target: { value: 'my-role-mapping' }, + }); + + (wrapper + .find(EuiComboBox) + .filter('[data-test-subj="roleMappingFormRoleComboBox"]') + .props() as any).onChange([{ label: 'foo_role' }]); + + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + + findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); + + expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ + name: 'my-role-mapping', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: { + all: [{ field: { username: '*' } }], + }, + metadata: {}, + }); + }); + + it('allows a role mapping to be updated', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.saveRoleMapping.mockResolvedValue(null); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { id: 'foo' }, + }, + ], + enabled: true, + rules: { + any: [{ field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }], + }, + metadata: { + foo: 'bar', + bar: 'baz', + }, + }); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + findTestSubject(wrapper, 'switchToRolesButton').simulate('click'); + + (wrapper + .find(EuiComboBox) + .filter('[data-test-subj="roleMappingFormRoleComboBox"]') + .props() as any).onChange([{ label: 'foo_role' }]); + + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + wrapper.find('button[id="addRuleOption"]').simulate('click'); + + findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); + + expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ + name: 'foo', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: { + any: [ + { field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }, + { field: { username: '*' } }, + ], + }, + metadata: { + foo: 'bar', + bar: 'baz', + }, + }); + }); + + it('renders a permission denied message when unauthorized to manage role mappings', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: false, + hasCompatibleRealms: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(PermissionDenied)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(PermissionDenied)).toHaveLength(1); + }); + + it('renders a warning when there are no compatible realms enabled', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: false, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); + }); + + it('renders a warning when editing a mapping with a stored role template, when stored scripts are disabled', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { id: 'foo' }, + }, + ], + enabled: true, + rules: { + field: { username: '*' }, + }, + }); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: false, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1); + }); + + it('renders a warning when editing a mapping with an inline role template, when inline scripts are disabled', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { source: 'foo' }, + }, + ], + enabled: true, + rules: { + field: { username: '*' }, + }, + }); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: false, + canUseStoredScripts: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + }); + + it('renders the visual editor by default for simple rule sets', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + roles: ['superuser'], + enabled: true, + rules: { + all: [ + { + field: { + username: '*', + }, + }, + { + field: { + dn: null, + }, + }, + { + field: { + realm: ['ldap', 'pki', null, 12], + }, + }, + ], + }, + }); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); + }); + + it('renders the JSON editor by default for complex rule sets', async () => { + const createRule = (depth: number): Record => { + if (depth > 0) { + const rule = { + all: [ + { + field: { + username: '*', + }, + }, + ], + } as Record; + + const subRule = createRule(depth - 1); + if (subRule) { + rule.all.push(subRule); + } + + return rule; + } + return null as any; + }; + + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + roles: ['superuser'], + enabled: true, + rules: createRule(10), + }); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx similarity index 87% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx index b8a75a4ad9fdf7..142b53cbb50f29 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx @@ -19,20 +19,21 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; -import { RoleMapping } from '../../../../../../common/model'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { NotificationsStart } from 'src/core/public'; +import { RoleMapping } from '../../../../common/model'; import { RuleEditorPanel } from './rule_editor_panel'; import { NoCompatibleRealms, PermissionDenied, DeleteProvider, SectionLoading, -} from '../../components'; -import { ROLE_MAPPINGS_PATH } from '../../../management_urls'; -import { validateRoleMappingForSave } from '../services/role_mapping_validation'; +} from '../components'; +import { RolesAPIClient } from '../../roles'; +import { ROLE_MAPPINGS_PATH } from '../../management_urls'; +import { validateRoleMappingForSave } from './services/role_mapping_validation'; import { MappingInfoPanel } from './mapping_info_panel'; -import { documentationLinks } from '../../services/documentation_links'; +import { DocumentationLinksService } from '../documentation_links'; +import { RoleMappingsAPIClient } from '../role_mappings_api_client'; interface State { loadState: 'loading' | 'permissionDenied' | 'ready' | 'saveInProgress'; @@ -50,7 +51,10 @@ interface State { interface Props { name?: string; - roleMappingsAPI: RoleMappingsAPI; + roleMappingsAPI: PublicMethodsOf; + rolesAPIClient: PublicMethodsOf; + notifications: NotificationsStart; + docLinks: DocumentationLinksService; } export class EditRoleMappingPage extends Component { @@ -74,6 +78,12 @@ export class EditRoleMappingPage extends Component { this.loadAppData(); } + public async componentDidUpdate(prevProps: Props) { + if (prevProps.name !== this.props.name) { + await this.loadAppData(); + } + } + public render() { const { loadState } = this.state; @@ -101,6 +111,8 @@ export class EditRoleMappingPage extends Component { validateForm={this.state.validateForm} canUseInlineScripts={this.state.canUseInlineScripts} canUseStoredScripts={this.state.canUseStoredScripts} + rolesAPIClient={this.props.rolesAPIClient} + docLinks={this.props.docLinks} /> { }, }) } + docLinks={this.props.docLinks} /> {this.getFormButtons()} @@ -149,7 +162,7 @@ export class EditRoleMappingPage extends Component { values={{ learnMoreLink: ( @@ -166,7 +179,7 @@ export class EditRoleMappingPage extends Component { {!this.state.hasCompatibleRealms && ( <> - + )} @@ -201,7 +214,10 @@ export class EditRoleMappingPage extends Component { {this.editingExistingRoleMapping() && ( - + {deleteRoleMappingsPrompt => { return ( { this.props.roleMappingsAPI .saveRoleMapping(this.state.roleMapping) .then(() => { - toastNotifications.addSuccess({ + this.props.notifications.toasts.addSuccess({ title: i18n.translate('xpack.security.management.editRoleMapping.saveSuccess', { defaultMessage: `Saved role mapping '{roleMappingName}'`, values: { @@ -264,7 +280,7 @@ export class EditRoleMappingPage extends Component { this.backToRoleMappingsList(); }) .catch(e => { - toastNotifications.addError(e, { + this.props.notifications.toasts.addError(e, { title: i18n.translate('xpack.security.management.editRoleMapping.saveError', { defaultMessage: `Error saving role mapping`, }), @@ -312,7 +328,7 @@ export class EditRoleMappingPage extends Component { roleMapping, }); } catch (e) { - toastNotifications.addDanger({ + this.props.notifications.toasts.addDanger({ title: i18n.translate( 'xpack.security.management.editRoleMapping.table.fetchingRoleMappingsErrorMessage', { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/index.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/index.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/index.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/index.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx similarity index 82% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx index d821b33ace6a7b..9b62ca27ca569c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx @@ -6,21 +6,27 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { MappingInfoPanel } from '.'; -import { RoleMapping } from '../../../../../../../common/model'; import { findTestSubject } from 'test_utils/find_test_subject'; +import { Role, RoleMapping } from '../../../../../common/model'; +import { RolesAPIClient } from '../../../roles'; +import { DocumentationLinksService } from '../../documentation_links'; import { RoleSelector } from '../role_selector'; import { RoleTemplateEditor } from '../role_selector/role_template_editor'; +import { MappingInfoPanel } from '.'; -jest.mock('../../../../../../lib/roles_api', () => { - return { - RolesApi: { - getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), - }, - }; -}); +import { rolesAPIClientMock } from '../../../roles/roles_api_client.mock'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; describe('MappingInfoPanel', () => { + let rolesAPI: PublicMethodsOf; + beforeEach(() => { + rolesAPI = rolesAPIClientMock.create(); + (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ + { name: 'foo_role' }, + { name: 'bar role' }, + ] as Role[]); + }); + it('renders when creating a role mapping, default to the "roles" view', () => { const props = { roleMapping: { @@ -32,6 +38,8 @@ describe('MappingInfoPanel', () => { metadata: {}, } as RoleMapping, mode: 'create', + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, } as MappingInfoPanel['props']; const wrapper = mountWithIntl(); @@ -77,6 +85,8 @@ describe('MappingInfoPanel', () => { metadata: {}, } as RoleMapping, mode: 'edit', + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, } as MappingInfoPanel['props']; const wrapper = mountWithIntl(); @@ -101,6 +111,8 @@ describe('MappingInfoPanel', () => { canUseInlineScripts: true, canUseStoredScripts: false, validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, }; const wrapper = mountWithIntl(); @@ -140,6 +152,8 @@ describe('MappingInfoPanel', () => { canUseInlineScripts: false, canUseStoredScripts: true, validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, }; const wrapper = mountWithIntl(); @@ -179,6 +193,8 @@ describe('MappingInfoPanel', () => { canUseInlineScripts: false, canUseStoredScripts: false, validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, }; const wrapper = mountWithIntl(); @@ -202,6 +218,8 @@ describe('MappingInfoPanel', () => { metadata: {}, } as RoleMapping, mode: 'edit', + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), + rolesAPIClient: rolesAPI, } as MappingInfoPanel['props']; const wrapper = mountWithIntl(); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx index a02b4fc1709f02..02af6bfbafa7e0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx @@ -18,14 +18,15 @@ import { EuiSwitch, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { RoleMapping } from '../../../../../../../common/model'; +import { RoleMapping } from '../../../../../common/model'; +import { RolesAPIClient } from '../../../roles'; import { validateRoleMappingName, validateRoleMappingRoles, validateRoleMappingRoleTemplates, -} from '../../services/role_mapping_validation'; +} from '../services/role_mapping_validation'; import { RoleSelector } from '../role_selector'; -import { documentationLinks } from '../../../services/documentation_links'; +import { DocumentationLinksService } from '../../documentation_links'; interface Props { roleMapping: RoleMapping; @@ -34,6 +35,8 @@ interface Props { validateForm: boolean; canUseInlineScripts: boolean; canUseStoredScripts: boolean; + rolesAPIClient: PublicMethodsOf; + docLinks: DocumentationLinksService; } interface State { @@ -163,6 +166,7 @@ export class MappingInfoPanel extends Component { > { defaultMessage="Create templates that describe the roles to assign to your users." />{' '} @@ -230,6 +234,7 @@ export class MappingInfoPanel extends Component { > { - return { - RolesApi: { - getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), - }, - }; -}); +import { RolesAPIClient } from '../../../roles'; +import { rolesAPIClientMock } from '../../../roles/roles_api_client.mock'; describe('RoleSelector', () => { + let rolesAPI: PublicMethodsOf; + beforeEach(() => { + rolesAPI = rolesAPIClientMock.create(); + (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ + { name: 'foo_role' }, + { name: 'bar role' }, + ] as Role[]); + }); + it('allows roles to be selected, removing any previously selected role templates', () => { const props = { roleMapping: { @@ -36,6 +39,7 @@ describe('RoleSelector', () => { canUseInlineScripts: true, onChange: jest.fn(), mode: 'roles', + rolesAPIClient: rolesAPI, } as RoleSelector['props']; const wrapper = mountWithIntl(); @@ -57,6 +61,7 @@ describe('RoleSelector', () => { canUseInlineScripts: true, onChange: jest.fn(), mode: 'templates', + rolesAPIClient: rolesAPI, } as RoleSelector['props']; const wrapper = mountWithIntl(); @@ -87,6 +92,7 @@ describe('RoleSelector', () => { canUseInlineScripts: true, onChange: jest.fn(), mode: 'templates', + rolesAPIClient: rolesAPI, } as RoleSelector['props']; const wrapper = mountWithIntl(); @@ -122,6 +128,7 @@ describe('RoleSelector', () => { canUseInlineScripts: true, onChange: jest.fn(), mode: 'templates', + rolesAPIClient: rolesAPI, } as RoleSelector['props']; const wrapper = mountWithIntl(); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx index 6b92d6b4907f16..992c2741ae93ee 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.tsx @@ -7,12 +7,13 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiComboBox, EuiFormRow, EuiHorizontalRule } from '@elastic/eui'; -import { RoleMapping, Role } from '../../../../../../../common/model'; -import { RolesApi } from '../../../../../../lib/roles_api'; +import { RoleMapping, Role } from '../../../../../common/model'; +import { RolesAPIClient } from '../../../roles'; import { AddRoleTemplateButton } from './add_role_template_button'; import { RoleTemplateEditor } from './role_template_editor'; interface Props { + rolesAPIClient: PublicMethodsOf; roleMapping: RoleMapping; canUseInlineScripts: boolean; canUseStoredScripts: boolean; @@ -32,7 +33,7 @@ export class RoleSelector extends React.Component { } public async componentDidMount() { - const roles = await RolesApi.getRoles(); + const roles = await this.props.rolesAPIClient.getRoles(); this.setState({ roles }); } diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx similarity index 98% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx index 4b8d34d2719960..d79651d7b9cd6d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.tsx @@ -18,12 +18,12 @@ import { EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { RoleTemplate } from '../../../../../../../common/model'; +import { RoleTemplate } from '../../../../../common/model'; import { isInlineRoleTemplate, isStoredRoleTemplate, isInvalidRoleTemplate, -} from '../../services/role_template_type'; +} from '../services/role_template_type'; import { RoleTemplateTypeSelect } from './role_template_type_select'; interface Props { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx similarity index 92% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx index 4a06af0fb436ba..aa65c5c9bcae74 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_type_select.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiComboBox } from '@elastic/eui'; -import { RoleTemplate } from '../../../../../../../common/model'; -import { isInlineRoleTemplate, isStoredRoleTemplate } from '../../services/role_template_type'; +import { RoleTemplate } from '../../../../../common/model'; +import { isInlineRoleTemplate, isStoredRoleTemplate } from '../services/role_template_type'; const templateTypeOptions = [ { diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_index.scss b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_index.scss new file mode 100644 index 00000000000000..c3b2764e647130 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_index.scss @@ -0,0 +1 @@ +@import './rule_editor_group'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_rule_editor_group.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/_rule_editor_group.scss diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx similarity index 97% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx index 917b822acef3f2..d1411bd9bf2b97 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { AddRuleButton } from './add_rule_button'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject } from 'test_utils/find_test_subject'; -import { FieldRule, AllRule } from '../../../model'; +import { FieldRule, AllRule } from '../../model'; describe('AddRuleButton', () => { it('allows a field rule to be created', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.tsx similarity index 97% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.tsx index 100c0dd3eeaee4..9696fa337a74fc 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { EuiButtonEmpty, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Rule, FieldRule, AllRule } from '../../../model'; +import { Rule, FieldRule, AllRule } from '../../model'; interface Props { onClick: (newRule: Rule) => void; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx index 8d5d5c99ee99d7..5374f4625336de 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { FieldRuleEditor } from './field_rule_editor'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { FieldRule } from '../../../model'; +import { FieldRule } from '../../model'; import { findTestSubject } from 'test_utils/find_test_subject'; import { ReactWrapper } from 'enzyme'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx index 52cf70dbd12bd2..2506c18dcaf3a0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.tsx @@ -18,7 +18,7 @@ import { EuiIcon, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FieldRule, FieldRuleValue } from '../../../model'; +import { FieldRule, FieldRuleValue } from '../../model'; interface Props { rule: FieldRule; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/index.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/index.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/index.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/index.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx index 8a9b37ab0f4065..263c456e901cd9 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx @@ -17,7 +17,10 @@ import { act } from 'react-dom/test-utils'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { JSONRuleEditor } from './json_rule_editor'; import { EuiCodeEditor } from '@elastic/eui'; -import { AllRule, AnyRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../../model'; +import { AllRule, AnyRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../model'; +import { DocumentationLinksService } from '../../documentation_links'; + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; describe('JSONRuleEditor', () => { it('renders an empty rule set', () => { @@ -25,6 +28,7 @@ describe('JSONRuleEditor', () => { rules: null, onChange: jest.fn(), onValidityChange: jest.fn(), + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); @@ -46,6 +50,7 @@ describe('JSONRuleEditor', () => { ]), onChange: jest.fn(), onValidityChange: jest.fn(), + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); @@ -79,6 +84,7 @@ describe('JSONRuleEditor', () => { rules: null, onChange: jest.fn(), onValidityChange: jest.fn(), + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); @@ -100,6 +106,7 @@ describe('JSONRuleEditor', () => { rules: null, onChange: jest.fn(), onValidityChange: jest.fn(), + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); @@ -130,6 +137,7 @@ describe('JSONRuleEditor', () => { rules: null, onChange: jest.fn(), onValidityChange: jest.fn(), + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx index 371fb59f7a5d13..e7a9149513d208 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx @@ -11,13 +11,14 @@ import 'brace/theme/github'; import { EuiCodeEditor, EuiFormRow, EuiButton, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { Rule, RuleBuilderError, generateRulesFromRaw } from '../../../model'; -import { documentationLinks } from '../../../services/documentation_links'; +import { DocumentationLinksService } from '../../documentation_links'; +import { Rule, RuleBuilderError, generateRulesFromRaw } from '../../model'; interface Props { rules: Rule | null; onChange: (updatedRules: Rule | null) => void; onValidityChange: (isValid: boolean) => void; + docLinks: DocumentationLinksService; } export const JSONRuleEditor = (props: Props) => { @@ -107,7 +108,7 @@ export const JSONRuleEditor = (props: Props) => { values={{ roleMappingAPI: ( diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx similarity index 88% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx index 809264183d30ca..b9c650cc1f77a4 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx @@ -15,8 +15,11 @@ import { findTestSubject } from 'test_utils/find_test_subject'; // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. import 'test_utils/stub_web_worker'; -import { AllRule, FieldRule } from '../../../model'; +import { AllRule, FieldRule } from '../../model'; import { EuiErrorBoundary } from '@elastic/eui'; +import { DocumentationLinksService } from '../../documentation_links'; + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; describe('RuleEditorPanel', () => { it('renders the visual editor when no rules are defined', () => { @@ -25,6 +28,7 @@ describe('RuleEditorPanel', () => { onChange: jest.fn(), onValidityChange: jest.fn(), validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); @@ -45,6 +49,7 @@ describe('RuleEditorPanel', () => { onChange: jest.fn(), onValidityChange: jest.fn(), validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); @@ -68,6 +73,7 @@ describe('RuleEditorPanel', () => { onChange: jest.fn(), onValidityChange: jest.fn(), validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); @@ -103,6 +109,7 @@ describe('RuleEditorPanel', () => { onChange: jest.fn(), onValidityChange: jest.fn(), validateForm: false, + docLinks: new DocumentationLinksService(coreMock.createStart().docLinks), }; const wrapper = mountWithIntl(); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx index 4aab49b2b2efcf..6e6641caa1f399 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx @@ -22,19 +22,20 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { RoleMapping } from '../../../../../../../common/model'; +import { RoleMapping } from '../../../../../common/model'; import { VisualRuleEditor } from './visual_rule_editor'; import { JSONRuleEditor } from './json_rule_editor'; -import { VISUAL_MAX_RULE_DEPTH } from '../../services/role_mapping_constants'; -import { Rule, generateRulesFromRaw } from '../../../model'; -import { validateRoleMappingRules } from '../../services/role_mapping_validation'; -import { documentationLinks } from '../../../services/documentation_links'; +import { VISUAL_MAX_RULE_DEPTH } from '../services/role_mapping_constants'; +import { Rule, generateRulesFromRaw } from '../../model'; +import { DocumentationLinksService } from '../../documentation_links'; +import { validateRoleMappingRules } from '../services/role_mapping_validation'; interface Props { rawRules: RoleMapping['rules']; onChange: (rawRules: RoleMapping['rules']) => void; onValidityChange: (isValid: boolean) => void; validateForm: boolean; + docLinks: DocumentationLinksService; } interface State { @@ -91,7 +92,7 @@ export class RuleEditorPanel extends Component { values={{ learnMoreLink: ( @@ -214,6 +215,7 @@ export class RuleEditorPanel extends Component { rules={this.state.rules} onChange={this.onRuleChange} onValidityChange={this.onValidityChange} + docLinks={this.props.docLinks} /> ); default: diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx index 3e0e0e386e98c2..5946aac4306b1f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { RuleGroupEditor } from './rule_group_editor'; import { shallowWithIntl, mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { AllRule, FieldRule, AnyRule, ExceptAnyRule } from '../../../model'; +import { AllRule, FieldRule, AnyRule, ExceptAnyRule } from '../../model'; import { FieldRuleEditor } from './field_rule_editor'; import { AddRuleButton } from './add_rule_button'; import { EuiContextMenuItem } from '@elastic/eui'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx similarity index 97% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx index 6fb33db179e8a6..c17a853a654676 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.tsx @@ -16,8 +16,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { AddRuleButton } from './add_rule_button'; import { RuleGroupTitle } from './rule_group_title'; import { FieldRuleEditor } from './field_rule_editor'; -import { RuleGroup, Rule, FieldRule } from '../../../model'; -import { isRuleGroup } from '../../services/is_rule_group'; +import { RuleGroup, Rule, FieldRule } from '../../model'; +import { isRuleGroup } from '../services/is_rule_group'; interface Props { rule: RuleGroup; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx similarity index 97% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx index e46893afd4d86e..6bef9c09eeef39 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx @@ -15,14 +15,7 @@ import { EuiConfirmModal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - RuleGroup, - AllRule, - AnyRule, - ExceptAllRule, - ExceptAnyRule, - FieldRule, -} from '../../../model'; +import { RuleGroup, AllRule, AnyRule, ExceptAllRule, ExceptAnyRule, FieldRule } from '../../model'; interface Props { rule: RuleGroup; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx index 7c63613ee1cc9e..b40f0063acb08a 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { VisualRuleEditor } from './visual_rule_editor'; import { findTestSubject } from 'test_utils/find_test_subject'; -import { AnyRule, AllRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../../model'; +import { AnyRule, AllRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../model'; import { RuleGroupEditor } from './rule_group_editor'; import { FieldRuleEditor } from './field_rule_editor'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx index 214c583189fb80..2e3db275788ee4 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.tsx @@ -9,9 +9,9 @@ import { EuiEmptyPrompt, EuiCallOut, EuiSpacer, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { FieldRuleEditor } from './field_rule_editor'; import { RuleGroupEditor } from './rule_group_editor'; -import { VISUAL_MAX_RULE_DEPTH } from '../../services/role_mapping_constants'; -import { Rule, FieldRule, RuleGroup, AllRule } from '../../../model'; -import { isRuleGroup } from '../../services/is_rule_group'; +import { VISUAL_MAX_RULE_DEPTH } from '../services/role_mapping_constants'; +import { Rule, FieldRule, RuleGroup, AllRule } from '../../model'; +import { isRuleGroup } from '../services/is_rule_group'; interface Props { rules: Rule | null; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/is_rule_group.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/is_rule_group.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/is_rule_group.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/is_rule_group.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts similarity index 98% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts index 9614c4338b631f..0c3f988ae6b105 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts @@ -10,7 +10,7 @@ import { validateRoleMappingRules, validateRoleMappingForSave, } from './role_mapping_validation'; -import { RoleMapping } from '../../../../../../common/model'; +import { RoleMapping } from '../../../../../common/model'; describe('validateRoleMappingName', () => { it('requires a value', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts similarity index 97% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts index 5916d6fd9e1891..7695f1da14881a 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { RoleMapping } from '../../../../../../common/model'; +import { RoleMapping } from '../../../../../common/model'; import { generateRulesFromRaw } from '../../model'; interface ValidationResult { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts index 8e1f47a4157ae9..c093bb1b3fbbc9 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts @@ -9,7 +9,7 @@ import { isInlineRoleTemplate, isInvalidRoleTemplate, } from './role_template_type'; -import { RoleTemplate } from '../../../../../../common/model'; +import { RoleTemplate } from '../../../../../common/model'; describe('#isStoredRoleTemplate', () => { it('returns true for stored templates, false otherwise', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts rename to x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts index 90d8d1a09e5877..5e646535a6c4d5 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts @@ -9,7 +9,7 @@ import { StoredRoleTemplate, InlineRoleTemplate, InvalidRoleTemplate, -} from '../../../../../../common/model'; +} from '../../../../../common/model'; export function isStoredRoleTemplate( roleMappingTemplate: RoleTemplate diff --git a/x-pack/plugins/security/public/management/role_mappings/index.mock.ts b/x-pack/plugins/security/public/management/role_mappings/index.mock.ts new file mode 100644 index 00000000000000..826477a1a5b153 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/index.mock.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { roleMappingsAPIClientMock } from './role_mappings_api_client.mock'; diff --git a/x-pack/plugins/security/public/management/role_mappings/index.ts b/x-pack/plugins/security/public/management/role_mappings/index.ts new file mode 100644 index 00000000000000..f670ea61810386 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { roleMappingsManagementApp } from './role_mappings_management_app'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap b/x-pack/plugins/security/public/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap rename to x-pack/plugins/security/public/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/all_rule.test.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/all_rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/any_rule.test.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/any_rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.test.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/except_all_rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.test.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/except_any_rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/field_rule.test.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/field_rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/field_rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/index.ts b/x-pack/plugins/security/public/management/role_mappings/model/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/index.ts rename to x-pack/plugins/security/public/management/role_mappings/model/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule.ts rename to x-pack/plugins/security/public/management/role_mappings/model/rule.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts rename to x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts index ebd48f6d15d99e..ad486a8b314c4f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.test.ts @@ -5,7 +5,7 @@ */ import { generateRulesFromRaw, FieldRule } from '.'; -import { RoleMapping } from '../../../../../common/model'; +import { RoleMapping } from '../../../../common/model'; import { RuleBuilderError } from './rule_builder_error'; describe('generateRulesFromRaw', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts rename to x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts index fe344b2ae38dd0..a384e61e521aba 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { RoleMapping } from '../../../../../common/model'; +import { RoleMapping } from '../../../../common/model'; import { FieldRule, FieldRuleValue } from './field_rule'; import { AllRule } from './all_rule'; import { AnyRule } from './any_rule'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder_error.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_builder_error.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder_error.ts rename to x-pack/plugins/security/public/management/role_mappings/model/rule_builder_error.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts b/x-pack/plugins/security/public/management/role_mappings/model/rule_group.ts similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts rename to x-pack/plugins/security/public/management/role_mappings/model/rule_group.ts index 3e1e7fad9b36f5..5077c79a543c46 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/rule_group.ts @@ -7,7 +7,7 @@ import { Rule } from './rule'; /** - * Represents a catagory of Role Mapping rules which are capable of containing other rules. + * Represents a category of Role Mapping rules which are capable of containing other rules. */ export abstract class RuleGroup extends Rule { /** diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.mock.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.mock.ts new file mode 100644 index 00000000000000..07d583d1e983f6 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.mock.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const roleMappingsAPIClientMock = { + create: () => ({ + checkRoleMappingFeatures: jest.fn(), + getRoleMappings: jest.fn(), + getRoleMapping: jest.fn(), + saveRoleMapping: jest.fn(), + deleteRoleMappings: jest.fn(), + }), +}; diff --git a/x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts similarity index 89% rename from x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts index b8bcba91388b53..0a88ed1da9ac3a 100644 --- a/x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/public'; -import { RoleMapping } from '../../common/model'; +import { HttpStart } from 'src/core/public'; +import { RoleMapping } from '../../../common/model'; interface CheckRoleMappingFeaturesResponse { canManageRoleMappings: boolean; @@ -20,8 +20,8 @@ type DeleteRoleMappingsResponse = Array<{ error?: Error; }>; -export class RoleMappingsAPI { - constructor(private readonly http: CoreSetup['http']) {} +export class RoleMappingsAPIClient { + constructor(private readonly http: HttpStart) {} public async checkRoleMappingFeatures(): Promise { return this.http.get(`/internal/security/_check_role_mapping_features`); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx similarity index 90% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx index 2342eeb97d03e3..6fe4bcc7a0bbb0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getCreateRoleMappingHref } from '../../../../management_urls'; +import { getCreateRoleMappingHref } from '../../../management_urls'; export const CreateRoleMappingButton = () => { return ( diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/index.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/index.ts rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/empty_prompt/empty_prompt.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/empty_prompt.tsx rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/empty_prompt/empty_prompt.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/index.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/empty_prompt/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/index.ts rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/empty_prompt/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/index.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/index.ts rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/index.ts diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx new file mode 100644 index 00000000000000..de0722b4cd85e2 --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx @@ -0,0 +1,219 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { RoleMappingsGridPage } from '.'; +import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../components'; +import { EmptyPrompt } from './empty_prompt'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { EuiLink } from '@elastic/eui'; +import { act } from '@testing-library/react'; +import { DocumentationLinksService } from '../documentation_links'; + +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; + +describe('RoleMappingsGridPage', () => { + it('renders an empty prompt when no role mappings exist', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMappings.mockResolvedValue([]); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(EmptyPrompt)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(EmptyPrompt)).toHaveLength(1); + }); + + it('renders a permission denied message when unauthorized to manage role mappings', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: false, + hasCompatibleRealms: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(PermissionDenied)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(PermissionDenied)).toHaveLength(1); + }); + + it('renders a warning when there are no compatible realms enabled', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMappings.mockResolvedValue([ + { + name: 'some realm', + enabled: true, + roles: [], + rules: { field: { username: '*' } }, + }, + ]); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: false, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); + }); + + it('renders links to mapped roles', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMappings.mockResolvedValue([ + { + name: 'some realm', + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + }, + ]); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + await nextTick(); + wrapper.update(); + + const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); + expect(links).toHaveLength(1); + expect(links.at(0).props()).toMatchObject({ + href: '#/management/security/roles/edit/superuser', + }); + }); + + it('describes the number of mapped role templates', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMappings.mockResolvedValue([ + { + name: 'some realm', + enabled: true, + role_templates: [{}, {}], + rules: { field: { username: '*' } }, + }, + ]); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + await nextTick(); + wrapper.update(); + + const templates = findTestSubject(wrapper, 'roleMappingRoles'); + expect(templates).toHaveLength(1); + expect(templates.text()).toEqual(`2 role templates defined`); + }); + + it('allows role mappings to be deleted, refreshing the grid after', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + roleMappingsAPI.getRoleMappings.mockResolvedValue([ + { + name: 'some-realm', + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + }, + ]); + roleMappingsAPI.checkRoleMappingFeatures.mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }); + roleMappingsAPI.deleteRoleMappings.mockResolvedValue([ + { + name: 'some-realm', + success: true, + }, + ]); + + const { docLinks, notifications } = coreMock.createStart(); + const wrapper = mountWithIntl( + + ); + await nextTick(); + wrapper.update(); + + expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(1); + expect(roleMappingsAPI.deleteRoleMappings).not.toHaveBeenCalled(); + + findTestSubject(wrapper, `deleteRoleMappingButton-some-realm`).simulate('click'); + expect(findTestSubject(wrapper, 'deleteRoleMappingConfirmationModal')).toHaveLength(1); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['some-realm']); + // Expect an additional API call to refresh the grid + expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx similarity index 93% rename from x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx rename to x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index 7b23f2288d1ba6..feb918cb6b301f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -25,24 +25,27 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { RoleMapping } from '../../../../../../common/model'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { NotificationsStart } from 'src/core/public'; +import { RoleMapping } from '../../../../common/model'; import { EmptyPrompt } from './empty_prompt'; import { NoCompatibleRealms, DeleteProvider, PermissionDenied, SectionLoading, -} from '../../components'; -import { documentationLinks } from '../../services/documentation_links'; +} from '../components'; import { getCreateRoleMappingHref, getEditRoleMappingHref, getEditRoleHref, -} from '../../../management_urls'; +} from '../../management_urls'; +import { DocumentationLinksService } from '../documentation_links'; +import { RoleMappingsAPIClient } from '../role_mappings_api_client'; interface Props { - roleMappingsAPI: RoleMappingsAPI; + roleMappingsAPI: PublicMethodsOf; + notifications: NotificationsStart; + docLinks: DocumentationLinksService; } interface State { @@ -140,7 +143,7 @@ export class RoleMappingsGridPage extends Component { values={{ learnMoreLink: ( @@ -168,7 +171,7 @@ export class RoleMappingsGridPage extends Component { {!this.state.hasCompatibleRealms && ( <> - + )} @@ -214,7 +217,10 @@ export class RoleMappingsGridPage extends Component { const search = { toolsLeft: selectedItems.length ? ( - + {deleteRoleMappingsPrompt => { return ( { return ( - + {deleteRoleMappingPrompt => { return ( ({ + RoleMappingsGridPage: (props: any) => `Role Mappings Page: ${JSON.stringify(props)}`, +})); + +jest.mock('./edit_role_mapping', () => ({ + EditRoleMappingPage: (props: any) => `Role Mapping Edit Page: ${JSON.stringify(props)}`, +})); + +import { roleMappingsManagementApp } from './role_mappings_management_app'; + +import { coreMock } from '../../../../../../src/core/public/mocks'; + +async function mountApp(basePath: string) { + const container = document.createElement('div'); + const setBreadcrumbs = jest.fn(); + + const unmount = await roleMappingsManagementApp + .create({ getStartServices: coreMock.createSetup().getStartServices as any }) + .mount({ basePath, element: container, setBreadcrumbs }); + + return { unmount, container, setBreadcrumbs }; +} + +describe('roleMappingsManagementApp', () => { + it('create() returns proper management app descriptor', () => { + expect( + roleMappingsManagementApp.create({ + getStartServices: coreMock.createSetup().getStartServices as any, + }) + ).toMatchInlineSnapshot(` + Object { + "id": "role_mappings", + "mount": [Function], + "order": 40, + "title": "Role Mappings", + } + `); + }); + + it('mount() works for the `grid` page', async () => { + const basePath = '/some-base-path/role_mappings'; + window.location.hash = basePath; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Role Mappings' }]); + expect(container).toMatchInlineSnapshot(` +
+ Role Mappings Page: {"notifications":{"toasts":{}},"roleMappingsAPI":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `create role mapping` page', async () => { + const basePath = '/some-base-path/role_mappings'; + window.location.hash = `${basePath}/edit`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Role Mappings' }, + { text: 'Create' }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `edit role mapping` page', async () => { + const basePath = '/some-base-path/role_mappings'; + const roleMappingName = 'someRoleMappingName'; + window.location.hash = `${basePath}/edit/${roleMappingName}`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `#/some-base-path/role_mappings/edit/${roleMappingName}`, text: roleMappingName }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() properly encodes role mapping name in `edit role mapping` page link in breadcrumbs', async () => { + const basePath = '/some-base-path/role_mappings'; + const roleMappingName = 'some 安全性 role mapping'; + window.location.hash = `${basePath}/edit/${roleMappingName}`; + + const { setBreadcrumbs } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Role Mappings' }, + { + href: + '#/some-base-path/role_mappings/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', + text: roleMappingName, + }, + ]); + }); +}); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx new file mode 100644 index 00000000000000..af1572cedbadef --- /dev/null +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup } from 'src/core/public'; +import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import { PluginStartDependencies } from '../../plugin'; +import { RolesAPIClient } from '../roles'; +import { RoleMappingsAPIClient } from './role_mappings_api_client'; +import { DocumentationLinksService } from './documentation_links'; +import { RoleMappingsGridPage } from './role_mappings_grid'; +import { EditRoleMappingPage } from './edit_role_mapping'; + +interface CreateParams { + getStartServices: CoreSetup['getStartServices']; +} + +export const roleMappingsManagementApp = Object.freeze({ + id: 'role_mappings', + create({ getStartServices }: CreateParams) { + return { + id: this.id, + order: 40, + title: i18n.translate('xpack.security.management.roleMappingsTitle', { + defaultMessage: 'Role Mappings', + }), + async mount({ basePath, element, setBreadcrumbs }) { + const [{ docLinks, http, notifications, i18n: i18nStart }] = await getStartServices(); + const roleMappingsBreadcrumbs = [ + { + text: i18n.translate('xpack.security.roleMapping.breadcrumb', { + defaultMessage: 'Role Mappings', + }), + href: `#${basePath}`, + }, + ]; + + const roleMappingsAPIClient = new RoleMappingsAPIClient(http); + const dockLinksService = new DocumentationLinksService(docLinks); + const RoleMappingsGridPageWithBreadcrumbs = () => { + setBreadcrumbs(roleMappingsBreadcrumbs); + return ( + + ); + }; + + const EditRoleMappingsPageWithBreadcrumbs = () => { + const { name } = useParams<{ name?: string }>(); + + setBreadcrumbs([ + ...roleMappingsBreadcrumbs, + name + ? { text: name, href: `#${basePath}/edit/${encodeURIComponent(name)}` } + : { + text: i18n.translate('xpack.security.roleMappings.createBreadcrumb', { + defaultMessage: 'Create', + }), + }, + ]); + + return ( + + ); + }; + + render( + + + + + + + + + + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; + }, + } as RegisterManagementAppArgs; + }, +}); diff --git a/x-pack/plugins/security/public/management/roles/_index.scss b/x-pack/plugins/security/public/management/roles/_index.scss new file mode 100644 index 00000000000000..5256c79f01f10f --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/_index.scss @@ -0,0 +1 @@ +@import './edit_role/index'; diff --git a/x-pack/plugins/security/public/management/roles/documentation_links.ts b/x-pack/plugins/security/public/management/roles/documentation_links.ts new file mode 100644 index 00000000000000..cf46973d3541ca --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/documentation_links.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DocLinksStart } from 'src/core/public'; + +export class DocumentationLinksService { + private readonly esDocBasePath: string; + + constructor(docLinks: DocLinksStart) { + this.esDocBasePath = `${docLinks.ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${docLinks.DOC_LINK_VERSION}/`; + } + + public getESClusterPrivilegesDocUrl() { + return `${this.esDocBasePath}security-privileges.html#privileges-list-cluster`; + } + + public getESRunAsPrivilegesDocUrl() { + return `${this.esDocBasePath}security-privileges.html#_run_as_privilege`; + } + + public getESIndicesPrivilegesDocUrl() { + return `${this.esDocBasePath}security-privileges.html#privileges-list-indices`; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/lib/__snapshots__/validate_role.test.ts.snap b/x-pack/plugins/security/public/management/roles/edit_role/__snapshots__/validate_role.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/lib/__snapshots__/validate_role.test.ts.snap rename to x-pack/plugins/security/public/management/roles/edit_role/__snapshots__/validate_role.test.ts.snap diff --git a/x-pack/plugins/security/public/management/roles/edit_role/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/_index.scss new file mode 100644 index 00000000000000..0153b1734ceba0 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/_index.scss @@ -0,0 +1,3 @@ +@import './collapsible_panel/index'; +@import './spaces_popover_list/index'; +@import './privileges/index'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/__snapshots__/collapsible_panel.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/__snapshots__/collapsible_panel.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/__snapshots__/collapsible_panel.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/__snapshots__/collapsible_panel.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/_collapsible_panel.scss b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_collapsible_panel.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/_collapsible_panel.scss rename to x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_collapsible_panel.scss diff --git a/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_index.scss new file mode 100644 index 00000000000000..c0f4f8ab9a8701 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/_index.scss @@ -0,0 +1 @@ +@import './collapsible_panel'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/collapsible_panel.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/collapsible_panel.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/collapsible_panel.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx index 416dd7f6c4e5c7..01af7cb4509f64 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/collapsible_panel.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx @@ -50,7 +50,6 @@ export class CollapsiblePanel extends Component { public getTitle = () => { return ( - // @ts-ignore diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/collapsible_panel/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx index cc16866c883555..f4af935be66487 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButtonEmpty, - // @ts-ignore - EuiConfirmModal, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DeleteRoleButton } from './delete_role_button'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.tsx b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx index 1ae84d3fb72242..c6a10396f235c4 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/delete_role_button.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx @@ -4,13 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButtonEmpty, - // @ts-ignore - EuiConfirmModal, - // @ts-ignore - EuiOverlayMask, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx new file mode 100644 index 00000000000000..2b3d7c811f6de3 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -0,0 +1,552 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactWrapper } from 'enzyme'; +import React from 'react'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { Capabilities } from 'src/core/public'; +import { Space } from '../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../features/public'; +// These modules should be moved into a common directory +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { Actions } from '../../../../server/authorization/actions'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { privilegesFactory } from '../../../../server/authorization/privileges'; +import { Role } from '../../../../common/model'; +import { DocumentationLinksService } from '../documentation_links'; +import { EditRolePage } from './edit_role_page'; +import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; +import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section'; + +import { TransformErrorSection } from './privileges/kibana/transform_error_section'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { licenseMock } from '../../../../common/licensing/index.mock'; +import { userAPIClientMock } from '../../users/index.mock'; +import { rolesAPIClientMock, indicesAPIClientMock, privilegesAPIClientMock } from '../index.mock'; + +const buildFeatures = () => { + return [ + { + id: 'feature1', + name: 'Feature 1', + icon: 'addDataApp', + app: ['feature1App'], + privileges: { + all: { + app: ['feature1App'], + ui: ['feature1-ui'], + savedObject: { + all: [], + read: [], + }, + }, + }, + }, + { + id: 'feature2', + name: 'Feature 2', + icon: 'addDataApp', + app: ['feature2App'], + privileges: { + all: { + app: ['feature2App'], + ui: ['feature2-ui'], + savedObject: { + all: ['feature2'], + read: ['config'], + }, + }, + }, + }, + ] as Feature[]; +}; + +const buildRawKibanaPrivileges = () => { + return privilegesFactory(new Actions('unit_test_version'), { + getFeatures: () => buildFeatures(), + }).get(); +}; + +const buildBuiltinESPrivileges = () => { + return { + cluster: ['all', 'manage', 'monitor'], + index: ['all', 'read', 'write', 'index'], + }; +}; + +const buildUICapabilities = (canManageSpaces = true) => { + return { + catalogue: {}, + management: {}, + navLinks: {}, + spaces: { + manage: canManageSpaces, + }, + } as Capabilities; +}; + +const buildSpaces = () => { + return [ + { + id: 'default', + name: 'Default', + disabledFeatures: [], + _reserved: true, + }, + { + id: 'space_1', + name: 'Space 1', + disabledFeatures: [], + }, + { + id: 'space_2', + name: 'Space 2', + disabledFeatures: ['feature2'], + }, + ] as Space[]; +}; + +const expectReadOnlyFormButtons = (wrapper: ReactWrapper) => { + expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(1); + expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(0); +}; + +const expectSaveFormButtons = (wrapper: ReactWrapper) => { + expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(0); + expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(1); +}; + +function getProps({ + action, + role, + canManageSpaces = true, + spacesEnabled = true, +}: { + action: 'edit' | 'clone'; + role?: Role; + canManageSpaces?: boolean; + spacesEnabled?: boolean; +}) { + const rolesAPIClient = rolesAPIClientMock.create(); + rolesAPIClient.getRole.mockResolvedValue(role); + + const indexPatterns = dataPluginMock.createStartContract().indexPatterns; + indexPatterns.getTitles = jest.fn().mockResolvedValue(['foo*', 'bar*']); + + const indicesAPIClient = indicesAPIClientMock.create(); + + const userAPIClient = userAPIClientMock.create(); + userAPIClient.getUsers.mockResolvedValue([]); + + const privilegesAPIClient = privilegesAPIClientMock.create(); + privilegesAPIClient.getAll.mockResolvedValue(buildRawKibanaPrivileges()); + privilegesAPIClient.getBuiltIn.mockResolvedValue(buildBuiltinESPrivileges()); + + const license = licenseMock.create(); + license.getFeatures.mockReturnValue({ + allowRoleDocumentLevelSecurity: true, + allowRoleFieldLevelSecurity: true, + } as any); + + const { fatalErrors } = coreMock.createSetup(); + const { http, docLinks, notifications } = coreMock.createStart(); + http.get.mockImplementation(async path => { + if (path === '/api/features') { + return buildFeatures(); + } + + if (path === '/api/spaces/space') { + return buildSpaces(); + } + }); + + return { + action, + roleName: role?.name, + license, + http, + indexPatterns, + indicesAPIClient, + privilegesAPIClient, + rolesAPIClient, + userAPIClient, + notifications, + docLinks: new DocumentationLinksService(docLinks), + fatalErrors, + spacesEnabled, + uiCapabilities: buildUICapabilities(canManageSpaces), + }; +} + +describe('', () => { + describe('with spaces enabled', () => { + it('can render a reserved role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectReadOnlyFormButtons(wrapper); + }); + + it('can render a user defined role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('can render when creating a new role', async () => { + const wrapper = mountWithIntl(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('can render when cloning an existing role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('renders an auth error when not authorized to manage spaces', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); + + expect( + wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') + ).toHaveLength(1); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expectSaveFormButtons(wrapper); + }); + + it('renders a partial read-only view when there is a transform error', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(TransformErrorSection)).toHaveLength(1); + expectReadOnlyFormButtons(wrapper); + }); + }); + + describe('with spaces disabled', () => { + it('can render a reserved role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectReadOnlyFormButtons(wrapper); + }); + + it('can render a user defined role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('can render when creating a new role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expectSaveFormButtons(wrapper); + }); + + it('can render when cloning an existing role', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expectSaveFormButtons(wrapper); + }); + + it('does not care if user cannot manage spaces', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); + + expect( + wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') + ).toHaveLength(0); + + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expectSaveFormButtons(wrapper); + }); + + it('renders a partial read-only view when there is a transform error', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(TransformErrorSection)).toHaveLength(1); + expectReadOnlyFormButtons(wrapper); + }); + }); + + it('can render if features are not available', async () => { + const { http } = coreMock.createStart(); + http.get.mockImplementation(async path => { + if (path === '/api/features') { + const error = { response: { status: 404 } }; + throw error; + } + + if (path === '/api/spaces/space') { + return buildSpaces(); + } + }); + + const wrapper = mountWithIntl(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('can render if index patterns are not available', async () => { + const indexPatterns = dataPluginMock.createStartContract().indexPatterns; + indexPatterns.getTitles = jest.fn().mockRejectedValue({ response: { status: 403 } }); + + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); +}); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx new file mode 100644 index 00000000000000..33e69a68ca8960 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -0,0 +1,594 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _, { get } from 'lodash'; +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { + ChangeEvent, + Fragment, + FunctionComponent, + HTMLProps, + useEffect, + useRef, + useState, +} from 'react'; +import { + Capabilities, + FatalErrorsSetup, + HttpStart, + IHttpFetchError, + NotificationsStart, +} from 'src/core/public'; +import { IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; +import { Space } from '../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../features/public'; +import { + KibanaPrivileges, + RawKibanaPrivileges, + Role, + BuiltinESPrivileges, + isReadOnlyRole as checkIfRoleReadOnly, + isReservedRole as checkIfRoleReserved, + copyRole, + prepareRoleClone, + RoleIndexPrivilege, +} from '../../../../common/model'; +import { ROLES_PATH } from '../../management_urls'; +import { RoleValidationResult, RoleValidator } from './validate_role'; +import { DeleteRoleButton } from './delete_role_button'; +import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; +import { ReservedRoleBadge } from './reserved_role_badge'; +import { SecurityLicense } from '../../../../common/licensing'; +import { UserAPIClient } from '../../users'; +import { DocumentationLinksService } from '../documentation_links'; +import { IndicesAPIClient } from '../indices_api_client'; +import { RolesAPIClient } from '../roles_api_client'; +import { PrivilegesAPIClient } from '../privileges_api_client'; + +interface Props { + action: 'edit' | 'clone'; + roleName?: string; + indexPatterns: IndexPatternsContract; + userAPIClient: PublicMethodsOf; + indicesAPIClient: PublicMethodsOf; + rolesAPIClient: PublicMethodsOf; + privilegesAPIClient: PublicMethodsOf; + docLinks: DocumentationLinksService; + http: HttpStart; + license: SecurityLicense; + spacesEnabled: boolean; + uiCapabilities: Capabilities; + notifications: NotificationsStart; + fatalErrors: FatalErrorsSetup; +} + +function useRunAsUsers( + userAPIClient: PublicMethodsOf, + fatalErrors: FatalErrorsSetup +) { + const [userNames, setUserNames] = useState(null); + useEffect(() => { + userAPIClient.getUsers().then( + users => setUserNames(users.map(user => user.username)), + err => fatalErrors.add(err) + ); + }, [fatalErrors, userAPIClient]); + + return userNames; +} + +function useIndexPatternsTitles( + indexPatterns: IndexPatternsContract, + fatalErrors: FatalErrorsSetup, + notifications: NotificationsStart +) { + const [indexPatternsTitles, setIndexPatternsTitles] = useState(null); + useEffect(() => { + indexPatterns + .getTitles() + .catch((err: IHttpFetchError) => { + // If user doesn't have access to the index patterns they still should be able to create new + // or edit existing role. + if (err.response?.status === 403) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.security.management.roles.noIndexPatternsPermission', { + defaultMessage: 'You need permission to access the list of available index patterns.', + }), + }); + return []; + } + + fatalErrors.add(err); + throw err; + }) + .then(setIndexPatternsTitles); + }, [fatalErrors, indexPatterns, notifications]); + + return indexPatternsTitles; +} + +function usePrivileges( + privilegesAPIClient: PublicMethodsOf, + fatalErrors: FatalErrorsSetup +) { + const [privileges, setPrivileges] = useState<[RawKibanaPrivileges, BuiltinESPrivileges] | null>( + null + ); + useEffect(() => { + Promise.all([ + privilegesAPIClient.getAll({ includeActions: true }), + privilegesAPIClient.getBuiltIn(), + ]).then( + ([kibanaPrivileges, builtInESPrivileges]) => + setPrivileges([kibanaPrivileges, builtInESPrivileges]), + err => fatalErrors.add(err) + ); + }, [privilegesAPIClient, fatalErrors]); + + return privileges; +} + +function useRole( + rolesAPIClient: PublicMethodsOf, + fatalErrors: FatalErrorsSetup, + notifications: NotificationsStart, + license: SecurityLicense, + action: string, + roleName?: string +) { + const [role, setRole] = useState(null); + useEffect(() => { + const rolePromise = roleName + ? rolesAPIClient.getRole(roleName) + : Promise.resolve({ + name: '', + elasticsearch: { cluster: [], indices: [], run_as: [] }, + kibana: [], + _unrecognized_applications: [], + } as Role); + + rolePromise + .then(fetchedRole => { + if (action === 'clone' && checkIfRoleReserved(fetchedRole)) { + backToRoleList(); + return; + } + + if (fetchedRole.elasticsearch.indices.length === 0) { + const emptyOption: RoleIndexPrivilege = { + names: [], + privileges: [], + }; + + const { + allowRoleDocumentLevelSecurity, + allowRoleFieldLevelSecurity, + } = license.getFeatures(); + + if (allowRoleFieldLevelSecurity) { + emptyOption.field_security = { + grant: ['*'], + except: [], + }; + } + + if (allowRoleDocumentLevelSecurity) { + emptyOption.query = ''; + } + + fetchedRole.elasticsearch.indices.push(emptyOption); + } + + setRole(action === 'clone' ? prepareRoleClone(fetchedRole) : copyRole(fetchedRole)); + }) + .catch((err: IHttpFetchError) => { + if (err.response?.status === 404) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.security.management.roles.roleNotFound', { + defaultMessage: 'No "{roleName}" role found.', + values: { roleName }, + }), + }); + backToRoleList(); + } else { + fatalErrors.add(err); + } + }); + }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license]); + + return [role, setRole] as [Role | null, typeof setRole]; +} + +function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup, spacesEnabled: boolean) { + const [spaces, setSpaces] = useState(null); + useEffect(() => { + (spacesEnabled ? http.get('/api/spaces/space') : Promise.resolve([])).then( + fetchedSpaces => setSpaces(fetchedSpaces), + err => fatalErrors.add(err) + ); + }, [http, fatalErrors, spacesEnabled]); + + return spaces; +} + +function useFeatures(http: HttpStart, fatalErrors: FatalErrorsSetup) { + const [features, setFeatures] = useState(null); + useEffect(() => { + http + .get('/api/features') + .catch((err: IHttpFetchError) => { + // Currently, the `/api/features` endpoint effectively requires the "Global All" kibana privilege (e.g., what + // the `kibana_user` grants), because it returns information about all registered features (#35841). It's + // possible that a user with `manage_security` will attempt to visit the role management page without the + // correct Kibana privileges. If that's the case, then they receive a partial view of the role, and the UI does + // not allow them to make changes to that role's kibana privileges. When this user visits the edit role page, + // this API endpoint will throw a 404, which causes view to fail completely. So we instead attempt to detect the + // 404 here, and respond in a way that still allows the UI to render itself. + const unauthorizedForFeatures = err.response?.status === 404; + if (unauthorizedForFeatures) { + return []; + } + + fatalErrors.add(err); + throw err; + }) + .then(setFeatures); + }, [http, fatalErrors]); + + return features; +} + +function backToRoleList() { + window.location.hash = ROLES_PATH; +} + +export const EditRolePage: FunctionComponent = ({ + userAPIClient, + indexPatterns, + rolesAPIClient, + indicesAPIClient, + privilegesAPIClient, + http, + roleName, + action, + fatalErrors, + spacesEnabled, + license, + docLinks, + uiCapabilities, + notifications, +}) => { + // We should keep the same mutable instance of Validator for every re-render since we'll + // eventually enable validation after the first time user tries to save a role. + const { current: validator } = useRef(new RoleValidator({ shouldValidate: false })); + + const [formError, setFormError] = useState(null); + const runAsUsers = useRunAsUsers(userAPIClient, fatalErrors); + const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors, notifications); + const privileges = usePrivileges(privilegesAPIClient, fatalErrors); + const spaces = useSpaces(http, fatalErrors, spacesEnabled); + const features = useFeatures(http, fatalErrors); + const [role, setRole] = useRole( + rolesAPIClient, + fatalErrors, + notifications, + license, + action, + roleName + ); + + if (!role || !runAsUsers || !indexPatternsTitles || !privileges || !spaces || !features) { + return null; + } + + const isEditingExistingRole = !!roleName && action === 'edit'; + const isReadOnlyRole = checkIfRoleReadOnly(role); + const isReservedRole = checkIfRoleReserved(role); + + const [kibanaPrivileges, builtInESPrivileges] = privileges; + + const getFormTitle = () => { + let titleText; + const props: HTMLProps = { + tabIndex: 0, + }; + if (isReservedRole) { + titleText = ( + + ); + props['aria-describedby'] = 'reservedRoleDescription'; + } else if (isEditingExistingRole) { + titleText = ( + + ); + } else { + titleText = ( + + ); + } + + return ( + +

+ {titleText} +

+
+ ); + }; + + const getActionButton = () => { + if (isEditingExistingRole && !isReadOnlyRole) { + return ( + + + + ); + } + + return null; + }; + + const getRoleName = () => { + return ( + + + } + helpText={ + !isReservedRole && isEditingExistingRole ? ( + + ) : ( + undefined + ) + } + {...validator.validateRoleName(role)} + > + + + + ); + }; + + const onNameChange = (e: ChangeEvent) => + setRole({ + ...role, + name: e.target.value.replace(/\s/g, '_'), + }); + + const getElasticsearchPrivileges = () => { + return ( +
+ + +
+ ); + }; + + const onRoleChange = (newRole: Role) => setRole(newRole); + + const getKibanaPrivileges = () => { + return ( +
+ + +
+ ); + }; + + const getFormButtons = () => { + if (isReadOnlyRole) { + return getReturnToRoleListButton(); + } + + return ( + + {getSaveButton()} + {getCancelButton()} + + {getActionButton()} + + ); + }; + + const getReturnToRoleListButton = () => { + return ( + + + + ); + }; + + const getSaveButton = () => { + const saveText = isEditingExistingRole ? ( + + ) : ( + + ); + + return ( + + {saveText} + + ); + }; + + const getCancelButton = () => { + return ( + + + + ); + }; + + const saveRole = async () => { + validator.enableValidation(); + + const result = validator.validateForSave(role); + if (result.isInvalid) { + setFormError(result); + } else { + setFormError(null); + + try { + await rolesAPIClient.saveRole({ role, spacesEnabled }); + } catch (error) { + notifications.toasts.addDanger(get(error, 'data.message')); + return; + } + + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage', + { defaultMessage: 'Saved role' } + ) + ); + + backToRoleList(); + } + }; + + const handleDeleteRole = async () => { + try { + await rolesAPIClient.deleteRole(role.name); + } catch (error) { + notifications.toasts.addDanger(get(error, 'data.message')); + return; + } + + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage', + { defaultMessage: 'Deleted role' } + ) + ); + + backToRoleList(); + }; + + const description = spacesEnabled ? ( + + ) : ( + + ); + + return ( +
+ + {getFormTitle()} + + + + {description} + + {isReservedRole && ( + + + +

+ +

+
+
+ )} + + + + {getRoleName()} + + {getElasticsearchPrivileges()} + + {getKibanaPrivileges()} + + + + {getFormButtons()} +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/index.ts diff --git a/x-pack/legacy/plugins/security/public/lib/privilege_utils.test.ts b/x-pack/plugins/security/public/management/roles/edit_role/privilege_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/lib/privilege_utils.test.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privilege_utils.test.ts diff --git a/x-pack/legacy/plugins/security/public/lib/privilege_utils.ts b/x-pack/plugins/security/public/management/roles/edit_role/privilege_utils.ts similarity index 93% rename from x-pack/legacy/plugins/security/public/lib/privilege_utils.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privilege_utils.ts index 74bde71dc421ab..3fd8536951967a 100644 --- a/x-pack/legacy/plugins/security/public/lib/privilege_utils.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privilege_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RoleKibanaPrivilege } from '../../common/model'; +import { RoleKibanaPrivilege } from '../../../../common/model'; /** * Determines if the passed privilege spec defines global privileges. diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/_index.scss new file mode 100644 index 00000000000000..a1a9d038065e61 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/_index.scss @@ -0,0 +1,2 @@ +@import './privilege_feature_icon'; +@import './kibana/index'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/_privilege_feature_icon.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/_privilege_feature_icon.scss new file mode 100644 index 00000000000000..a7f24c96a28216 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/_privilege_feature_icon.scss @@ -0,0 +1,4 @@ +.secPrivilegeFeatureIcon { + flex-shrink: 0; + margin-right: $euiSizeS; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap similarity index 87% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap index 795131337c31fd..323629de7578d7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap @@ -15,7 +15,7 @@ exports[`it renders without crashing 1`] = ` /> { + expect(shallowWithIntl()).toMatchSnapshot(); +}); + +test('it renders ClusterPrivileges', () => { + expect( + mountWithIntl().find(ClusterPrivileges) + ).toHaveLength(1); +}); + +test('it renders IndexPrivileges', () => { + expect( + mountWithIntl().find(IndexPrivileges) + ).toHaveLength(1); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx index c0e6db3fef21c8..96249ccf3ff87e 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx @@ -7,7 +7,6 @@ import { EuiButton, EuiComboBox, - // @ts-ignore EuiDescribedFormGroup, EuiFormRow, EuiHorizontalRule, @@ -19,26 +18,26 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Role, BuiltinESPrivileges } from '../../../../../../../common/model'; -// @ts-ignore -import { documentationLinks } from '../../../../../../documentation_links'; -import { RoleValidator } from '../../../lib/validate_role'; +import { Role, BuiltinESPrivileges } from '../../../../../../common/model'; +import { SecurityLicense } from '../../../../../../common/licensing'; +import { IndicesAPIClient } from '../../../indices_api_client'; +import { RoleValidator } from '../../validate_role'; import { CollapsiblePanel } from '../../collapsible_panel'; import { ClusterPrivileges } from './cluster_privileges'; - import { IndexPrivileges } from './index_privileges'; +import { DocumentationLinksService } from '../../../documentation_links'; interface Props { role: Role; editable: boolean; - httpClient: any; + indicesAPIClient: PublicMethodsOf; + docLinks: DocumentationLinksService; + license: SecurityLicense; onChange: (role: Role) => void; runAsUsers: string[]; validator: RoleValidator; builtinESPrivileges: BuiltinESPrivileges; indexPatterns: string[]; - allowDocumentLevelSecurity: boolean; - allowFieldLevelSecurity: boolean; } export class ElasticsearchPrivileges extends Component { @@ -53,23 +52,22 @@ export class ElasticsearchPrivileges extends Component { public getForm = () => { const { role, - httpClient, + indicesAPIClient, + docLinks, validator, onChange, editable, indexPatterns, - allowDocumentLevelSecurity, - allowFieldLevelSecurity, + license, builtinESPrivileges, } = this.props; const indexProps = { role, - httpClient, + indicesAPIClient, validator, indexPatterns, - allowDocumentLevelSecurity, - allowFieldLevelSecurity, + license, onChange, availableIndexPrivileges: builtinESPrivileges.index, }; @@ -91,7 +89,7 @@ export class ElasticsearchPrivileges extends Component { id="xpack.security.management.editRole.elasticSearchPrivileges.manageRoleActionsDescription" defaultMessage="Manage the actions this role can perform against your cluster. " /> - {this.learnMore(documentationLinks.esClusterPrivileges)} + {this.learnMore(docLinks.getESClusterPrivilegesDocUrl())}

} > @@ -121,7 +119,7 @@ export class ElasticsearchPrivileges extends Component { id="xpack.security.management.editRole.elasticSearchPrivileges.howToBeSubmittedOnBehalfOfOtherUsersDescription" defaultMessage="Allow requests to be submitted on the behalf of other users. " /> - {this.learnMore(documentationLinks.esRunAsPrivileges)} + {this.learnMore(docLinks.getESRunAsPrivilegesDocUrl())}

} > @@ -165,7 +163,7 @@ export class ElasticsearchPrivileges extends Component { id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToClusterDataDescription" defaultMessage="Control access to the data in your cluster. " /> - {this.learnMore(documentationLinks.esIndicesPrivileges)} + {this.learnMore(docLinks.getESIndicesPrivilegesDocUrl())}

diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx index 6d386fd78a11b1..5e2da513143653 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx @@ -6,7 +6,7 @@ import { EuiButtonIcon, EuiTextArea } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { RoleValidator } from '../../../lib/validate_role'; +import { RoleValidator } from '../../validate_role'; import { IndexPrivilegeForm } from './index_privilege_form'; test('it renders without crashing', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx similarity index 98% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx index bafc56dc167ea5..15e0367c2b6dc1 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx @@ -19,8 +19,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { ChangeEvent, Component, Fragment } from 'react'; -import { RoleIndexPrivilege } from '../../../../../../../common/model'; -import { RoleValidator } from '../../../lib/validate_role'; +import { RoleIndexPrivilege } from '../../../../../../common/model'; +import { RoleValidator } from '../../validate_role'; const fromOption = (option: any) => option.label; const toOption = (value: string) => ({ label: value }); @@ -164,7 +164,6 @@ export class IndexPrivilegeForm extends Component { {!isReadOnlyRole && ( { - // @ts-ignore missing "compressed" prop definition { } return ( - // @ts-ignore {!this.props.isReadOnlyRole && ( { - // @ts-ignore missing "compressed" proptype new Promise(setImmediate); test('it renders without crashing', async () => { + const license = licenseMock.create(); + license.getFeatures.mockReturnValue({ + allowRoleFieldLevelSecurity: true, + allowRoleDocumentLevelSecurity: true, + } as any); + const props = { role: { name: '', @@ -25,14 +34,13 @@ test('it renders without crashing', async () => { run_as: [], }, }, - httpClient: jest.fn(), onChange: jest.fn(), indexPatterns: [], editable: true, - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, validator: new RoleValidator(), availableIndexPrivileges: ['all', 'read', 'write', 'index'], + indicesAPIClient: indicesAPIClientMock.create(), + license, }; const wrapper = shallowWithIntl(); await flushPromises(); @@ -40,6 +48,12 @@ test('it renders without crashing', async () => { }); test('it renders a IndexPrivilegeForm for each privilege on the role', async () => { + const license = licenseMock.create(); + license.getFeatures.mockReturnValue({ + allowRoleFieldLevelSecurity: true, + allowRoleDocumentLevelSecurity: true, + } as any); + const props = { role: { name: '', @@ -59,14 +73,13 @@ test('it renders a IndexPrivilegeForm for each privilege on the role', async () run_as: [], }, }, - httpClient: jest.fn(), onChange: jest.fn(), indexPatterns: [], editable: true, - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, validator: new RoleValidator(), availableIndexPrivileges: ['all', 'read', 'write', 'index'], + indicesAPIClient: indicesAPIClientMock.create(), + license, }; const wrapper = mountWithIntl(); await flushPromises(); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx similarity index 84% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx index f09084ad2bb382..2c745067fede2d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.tsx @@ -5,19 +5,23 @@ */ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { Role, RoleIndexPrivilege } from '../../../../../../../common/model'; -import { isReadOnlyRole, isRoleEnabled } from '../../../../../../lib/role_utils'; -import { getFields } from '../../../../../../objects'; -import { RoleValidator } from '../../../lib/validate_role'; +import { + Role, + RoleIndexPrivilege, + isReadOnlyRole, + isRoleEnabled, +} from '../../../../../../common/model'; +import { SecurityLicense } from '../../../../../../common/licensing'; +import { IndicesAPIClient } from '../../../indices_api_client'; +import { RoleValidator } from '../../validate_role'; import { IndexPrivilegeForm } from './index_privilege_form'; interface Props { role: Role; indexPatterns: string[]; availableIndexPrivileges: string[]; - allowDocumentLevelSecurity: boolean; - allowFieldLevelSecurity: boolean; - httpClient: any; + indicesAPIClient: PublicMethodsOf; + license: SecurityLicense; onChange: (role: Role) => void; validator: RoleValidator; } @@ -43,20 +47,16 @@ export class IndexPrivileges extends Component { public render() { const { indices = [] } = this.props.role.elasticsearch; - const { - indexPatterns, - allowDocumentLevelSecurity, - allowFieldLevelSecurity, - availableIndexPrivileges, - } = this.props; + const { indexPatterns, license, availableIndexPrivileges } = this.props; + const { allowRoleDocumentLevelSecurity, allowRoleFieldLevelSecurity } = license.getFeatures(); const props = { indexPatterns, // If editing an existing role while that has been disabled, always show the FLS/DLS fields because currently // a role is only marked as disabled if it has FLS/DLS setup (usually before the user changed to a license that // doesn't permit FLS/DLS). - allowDocumentLevelSecurity: allowDocumentLevelSecurity || !isRoleEnabled(this.props.role), - allowFieldLevelSecurity: allowFieldLevelSecurity || !isRoleEnabled(this.props.role), + allowDocumentLevelSecurity: allowRoleDocumentLevelSecurity || !isRoleEnabled(this.props.role), + allowFieldLevelSecurity: allowRoleFieldLevelSecurity || !isRoleEnabled(this.props.role), isReadOnlyRole: isReadOnlyRole(this.props.role), }; @@ -171,7 +171,7 @@ export class IndexPrivileges extends Component { try { return { - [pattern]: await getFields(this.props.httpClient, pattern), + [pattern]: await this.props.indicesAPIClient.getFields(pattern), }; } catch (e) { return { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/_index.scss new file mode 100644 index 00000000000000..19547c0e1953e0 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/_index.scss @@ -0,0 +1,2 @@ +@import './feature_table/index'; +@import './space_aware_privilege_section/index'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/lib/constants.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/constants.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/lib/constants.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/constants.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/__snapshots__/feature_table.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__snapshots__/feature_table.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/__snapshots__/feature_table.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__snapshots__/feature_table.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_change_all_privileges.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/_index.scss rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_change_all_privileges.scss diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_index.scss new file mode 100644 index 00000000000000..6a96553742819f --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/_index.scss @@ -0,0 +1 @@ +@import './change_all_privileges'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/change_all_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/change_all_privileges.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/change_all_privileges.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx index 9648bf1d111bf4..dea42e16f99d42 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx @@ -3,12 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore import { EuiInMemoryTable } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; +import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { FeatureTable } from './feature_table'; const defaultPrivilegeDefinition = new KibanaPrivileges({ @@ -113,7 +112,6 @@ describe('FeatureTable', () => { onChange={jest.fn()} onChangeAll={jest.fn()} spacesIndex={0} - intl={null as any} /> ); @@ -141,7 +139,6 @@ describe('FeatureTable', () => { onChange={jest.fn()} onChangeAll={jest.fn()} spacesIndex={-1} - intl={null as any} /> ); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx similarity index 91% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index a05dc687fce4ac..8283efe23260af 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -4,28 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; +import React, { Component } from 'react'; import { - // @ts-ignore EuiButtonGroup, EuiIcon, EuiIconTip, - // @ts-ignore EuiInMemoryTable, EuiText, IconType, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; -import _ from 'lodash'; -import React, { Component } from 'react'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../../common/model'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Feature } from '../../../../../../../../features/public'; +import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../common/model'; import { AllowedPrivilege, CalculatedPrivilege, PrivilegeExplanation, -} from '../../../../../../../lib/kibana_privilege_calculator'; -import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; -import { NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; +} from '../kibana_privilege_calculator'; +import { isGlobalPrivilegeDefinition } from '../../../privilege_utils'; +import { NO_PRIVILEGE_VALUE } from '../constants'; import { PrivilegeDisplay } from '../space_aware_privilege_section/privilege_display'; import { ChangeAllPrivilegesControl } from './change_all_privileges'; @@ -36,7 +35,6 @@ interface Props { allowedPrivileges: AllowedPrivilege; rankedFeaturePrivileges: FeaturesPrivileges; kibanaPrivileges: KibanaPrivileges; - intl: InjectedIntl; spacesIndex: number; onChange: (featureId: string, privileges: string[]) => void; onChangeAll: (privileges: string[]) => void; @@ -100,9 +98,7 @@ export class FeatureTable extends Component { const availablePrivileges = Object.values(rankedFeaturePrivileges)[0]; return ( - // @ts-ignore missing responsive from typedef { private getColumns = (availablePrivileges: string[]) => [ { field: 'feature', - name: this.props.intl.formatMessage({ - id: 'xpack.security.management.editRole.featureTable.enabledRoleFeaturesFeatureColumnTitle', - defaultMessage: 'Feature', - }), + name: i18n.translate( + 'xpack.security.management.editRole.featureTable.enabledRoleFeaturesFeatureColumnTitle', + { defaultMessage: 'Feature' } + ), render: (feature: TableFeature) => { let tooltipElement = null; if (feature.privilegesTooltip) { @@ -239,9 +235,7 @@ export class FeatureTable extends Component { }); return ( - // @ts-ignore missing name from typedef void; validator: RoleValidator; - intl: InjectedIntl; } export class KibanaPrivilegesRegion extends Component { @@ -81,7 +79,6 @@ export class KibanaPrivilegesRegion extends Component { privilegeCalculatorFactory={privilegeCalculatorFactory} onChange={onChange} editable={editable} - intl={this.props.intl} /> ); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/privilege_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/privilege_selector.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx index 135419cc9a10d2..bda0227372c094 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/privilege_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx @@ -6,7 +6,7 @@ import { EuiSelect } from '@elastic/eui'; import React, { ChangeEvent, Component } from 'react'; -import { NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; +import { NO_PRIVILEGE_VALUE } from '../constants'; interface Props { ['data-test-subj']: string; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index f97fa93294ff5b..db1e3cfd616212 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore + import { EuiButtonGroup, EuiButtonGroupProps, EuiComboBox, EuiSuperSelect } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; +import { Feature } from '../../../../../../../../features/public'; +import { KibanaPrivileges, Role } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { SimplePrivilegeSection } from './simple_privilege_section'; import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index 7768dc769a32f7..2221fc6bab2797 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -6,21 +6,24 @@ import { EuiComboBox, - // @ts-ignore EuiDescribedFormGroup, EuiFormRow, EuiSuperSelect, EuiText, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; -import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; -import { copyRole } from '../../../../../../../lib/role_utils'; -import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; +import { Feature } from '../../../../../../../../features/public'; +import { + KibanaPrivileges, + Role, + RoleKibanaPrivilege, + copyRole, +} from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; +import { isGlobalPrivilegeDefinition } from '../../../privilege_utils'; +import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../constants'; import { FeatureTable } from '../feature_table'; import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning'; @@ -31,7 +34,6 @@ interface Props { features: Feature[]; onChange: (role: Role) => void; editable: boolean; - intl: InjectedIntl; } interface State { @@ -230,7 +232,6 @@ export class SimplePrivilegeSection extends Component { allowedPrivileges={allowedPrivileges} rankedFeaturePrivileges={privilegeCalculator.rankedFeaturePrivileges} features={this.props.features} - intl={this.props.intl} onChange={this.onFeaturePrivilegeChange} onChangeAll={this.onChangeAllFeaturePrivileges} spacesIndex={this.props.role.kibana.findIndex(k => isGlobalPrivilegeDefinition(k))} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__fixtures__/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__fixtures__/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__fixtures__/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts index d412ba63403e19..428836c9f181b9 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__fixtures__/raw_kibana_privileges.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RawKibanaPrivileges } from '../../../../../../../../../common/model'; +import { RawKibanaPrivileges } from '../../../../../../../../common/model'; export const rawKibanaPrivileges: RawKibanaPrivileges = { global: { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_display.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_display.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_display.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_display.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap similarity index 82% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap index c20a391cdb20c5..e9f2f946e98859 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap @@ -351,108 +351,6 @@ exports[` renders without crashing 1`] = ` } disabled={true} features={Array []} - intl={ - Object { - "defaultFormats": Object {}, - "defaultLocale": "en", - "formatDate": [Function], - "formatHTMLMessage": [Function], - "formatMessage": [Function], - "formatNumber": [Function], - "formatPlural": [Function], - "formatRelative": [Function], - "formatTime": [Function], - "formats": Object { - "date": Object { - "full": Object { - "day": "numeric", - "month": "long", - "weekday": "long", - "year": "numeric", - }, - "long": Object { - "day": "numeric", - "month": "long", - "year": "numeric", - }, - "medium": Object { - "day": "numeric", - "month": "short", - "year": "numeric", - }, - "short": Object { - "day": "numeric", - "month": "numeric", - "year": "2-digit", - }, - }, - "number": Object { - "currency": Object { - "style": "currency", - }, - "percent": Object { - "style": "percent", - }, - }, - "relative": Object { - "days": Object { - "units": "day", - }, - "hours": Object { - "units": "hour", - }, - "minutes": Object { - "units": "minute", - }, - "months": Object { - "units": "month", - }, - "seconds": Object { - "units": "second", - }, - "years": Object { - "units": "year", - }, - }, - "time": Object { - "full": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "long": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "medium": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - }, - "short": Object { - "hour": "numeric", - "minute": "numeric", - }, - }, - }, - "formatters": Object { - "getDateTimeFormat": [Function], - "getMessageFormat": [Function], - "getNumberFormat": [Function], - "getPluralFormat": [Function], - "getRelativeFormat": [Function], - }, - "locale": "en", - "messages": Object {}, - "now": [Function], - "onError": [Function], - "textComponent": Symbol(react.fragment), - "timeZone": null, - } - } kibanaPrivileges={ KibanaPrivileges { "rawKibanaPrivileges": Object { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/_index.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/_index.scss rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/_index.scss diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss similarity index 87% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss index 5f9fbced5ee6ac..8f47727fdf8d62 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/_privilege_matrix.scss @@ -9,6 +9,6 @@ .secPrivilegeMatrix__row--isBasePrivilege, .secPrivilegeMatrix__cell--isGlobalPrivilege, -.secPrivilegeTable__row--isGlobalSpace, { +.secPrivilegeTable__row--isGlobalSpace { background-color: $euiColorLightestShade; } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx index 62e22050132fd7..c6268e19abfd1f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx @@ -7,7 +7,7 @@ import { EuiIconTip, EuiText, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { PRIVILEGE_SOURCE } from '../../../../../../../lib/kibana_privilege_calculator'; +import { PRIVILEGE_SOURCE } from '../kibana_privilege_calculator'; import { PrivilegeDisplay } from './privilege_display'; describe('PrivilegeDisplay', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx index 6af7672f6fef86..55ac99da4c8c16 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.tsx @@ -7,11 +7,8 @@ import { EuiIcon, EuiIconTip, EuiText, IconType, PropsOf, EuiToolTip } from '@el import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { ReactNode, FC } from 'react'; -import { - PRIVILEGE_SOURCE, - PrivilegeExplanation, -} from '../../../../../../../lib/kibana_privilege_calculator'; -import { NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; +import { PRIVILEGE_SOURCE, PrivilegeExplanation } from '../kibana_privilege_calculator'; +import { NO_PRIVILEGE_VALUE } from '../constants'; interface Props extends PropsOf { privilege: string | string[] | undefined; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx index ee121caa13a2af..16aad4826ae44c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore + import { EuiButtonEmpty, EuiInMemoryTable } from '@elastic/eui'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../..//lib/kibana_privilege_calculator'; +import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../../../../features/public'; +import { KibanaPrivileges, Role } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { PrivilegeMatrix } from './privilege_matrix'; describe('PrivilegeMatrix', () => { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx similarity index 93% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index 962487312c83df..b3449e32c6c917 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -8,7 +8,6 @@ import { EuiButtonEmpty, EuiIcon, EuiIconTip, - // @ts-ignore EuiInMemoryTable, EuiModal, EuiModalBody, @@ -16,18 +15,16 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiOverlayMask, - // @ts-ignore - EuiToolTip, IconType, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { SpaceAvatar } from '../../../../../../../../../spaces/public/space_avatar'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { FeaturesPrivileges, Role } from '../../../../../../../../common/model'; -import { CalculatedPrivilege } from '../../../../../../../lib/kibana_privilege_calculator'; -import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; +import { SpaceAvatar } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; +import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../../../../features/public'; +import { FeaturesPrivileges, Role } from '../../../../../../../common/model'; +import { CalculatedPrivilege } from '../kibana_privilege_calculator'; +import { isGlobalPrivilegeDefinition } from '../../../privilege_utils'; import { SpacesPopoverList } from '../../../spaces_popover_list'; import { PrivilegeDisplay } from './privilege_display'; @@ -258,11 +255,9 @@ export class PrivilegeMatrix extends Component { ]; return ( - // @ts-ignore missing rowProps from typedef { return { className: item.feature.isBase ? 'secPrivilegeMatrix__row--isBasePrivilege' : '', diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 2b7d87f663d729..675f02a81f9e1d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -9,8 +9,8 @@ import { merge } from 'lodash'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; -import { KibanaPrivileges } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; +import { KibanaPrivileges } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { PrivilegeSpaceForm } from './privilege_space_form'; import { rawKibanaPrivileges } from './__fixtures__'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index 5abb87d23bb6e6..6d1f5117c52e98 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -24,17 +24,16 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; +import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../../../../features/public'; +import { KibanaPrivileges, Role, copyRole } from '../../../../../../../common/model'; import { AllowedPrivilege, KibanaPrivilegeCalculatorFactory, PrivilegeExplanation, -} from '../../../../../../../lib/kibana_privilege_calculator'; -import { hasAssignedFeaturePrivileges } from '../../../../../../../lib/privilege_utils'; -import { copyRole } from '../../../../../../../lib/role_utils'; -import { CUSTOM_PRIVILEGE_VALUE } from '../../../../lib/constants'; +} from '../kibana_privilege_calculator'; +import { hasAssignedFeaturePrivileges } from '../../../privilege_utils'; +import { CUSTOM_PRIVILEGE_VALUE } from '../constants'; import { FeatureTable } from '../feature_table'; import { SpaceSelector } from './space_selector'; @@ -285,7 +284,6 @@ export class PrivilegeSpaceForm extends Component { calculatedPrivileges={calculatedPrivileges} allowedPrivileges={allowedPrivileges} rankedFeaturePrivileges={privilegeCalculator.rankedFeaturePrivileges} - intl={this.props.intl} onChange={this.onFeaturePrivilegesChange} onChangeAll={this.onChangeAllFeaturePrivileges} kibanaPrivileges={this.props.kibanaPrivileges} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx similarity index 99% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index 37ee43c5473b0d..f0a391c98c9100 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -10,8 +10,8 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { PrivilegeSpaceTable } from './privilege_space_table'; import { PrivilegeDisplay } from './privilege_display'; -import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; +import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { rawKibanaPrivileges } from './__fixtures__'; interface TableRow { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx similarity index 94% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index 65a3df9fb47a1a..1c27ec84f50dcd 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -12,22 +12,21 @@ import { EuiBasicTableColumn, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; -import _ from 'lodash'; import React, { Component } from 'react'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; +import { getSpaceColor } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; +import { Space } from '../../../../../../../../spaces/common/model/space'; import { FeaturesPrivileges, Role, RoleKibanaPrivilege, -} from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; + copyRole, +} from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition, hasAssignedFeaturePrivileges, -} from '../../../../../../../lib/privilege_utils'; -import { copyRole } from '../../../../../../../lib/role_utils'; -import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; +} from '../../../privilege_utils'; +import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../constants'; import { SpacesPopoverList } from '../../../spaces_popover_list'; import { PrivilegeDisplay } from './privilege_display'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx index 2756b1c4472744..e06d2a4f7dc337 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { KibanaPrivileges } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; -import { RoleValidator } from '../../../../lib/validate_role'; +import { KibanaPrivileges } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; +import { RoleValidator } from '../../../validate_role'; import { PrivilegeMatrix } from './privilege_matrix'; import { PrivilegeSpaceForm } from './privilege_space_form'; import { PrivilegeSpaceTable } from './privilege_space_table'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx similarity index 93% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index d324cf99c8418a..21cadfafe1790b 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -14,13 +14,12 @@ import { import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/public'; -import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; -import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; -import { isReservedRole } from '../../../../../../../lib/role_utils'; -import { RoleValidator } from '../../../../lib/validate_role'; +import { Capabilities } from 'src/core/public'; +import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Feature } from '../../../../../../../../features/public'; +import { KibanaPrivileges, Role, isReservedRole } from '../../../../../../../common/model'; +import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; +import { RoleValidator } from '../../../validate_role'; import { PrivilegeMatrix } from './privilege_matrix'; import { PrivilegeSpaceForm } from './privilege_space_form'; import { PrivilegeSpaceTable } from './privilege_space_table'; @@ -34,7 +33,7 @@ interface Props { editable: boolean; validator: RoleValidator; intl: InjectedIntl; - uiCapabilities: UICapabilities; + uiCapabilities: Capabilities; features: Feature[]; } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx similarity index 93% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx index 0eb9cf0b0ee9d7..cfeb5b9f37d8ce 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -7,8 +7,8 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiHealth, EuiHighlight } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; +import { getSpaceColor } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; +import { Space } from '../../../../../../../../spaces/common/model/space'; const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => { if (!space) { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/transform_error_section/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/transform_error_section/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/transform_error_section/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/transform_error_section/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/transform_error_section/transform_error_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/transform_error_section/transform_error_section.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/transform_error_section/transform_error_section.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/transform_error_section/transform_error_section.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.test.tsx similarity index 96% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.test.tsx index 9b483d92cde41b..d29b442420a90c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.test.tsx @@ -7,7 +7,7 @@ import { EuiIcon } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; -import { Role } from '../../../../../common/model'; +import { Role } from '../../../../common/model'; import { ReservedRoleBadge } from './reserved_role_badge'; const reservedRole: Role = { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.tsx b/x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.tsx similarity index 89% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.tsx index 3d817d1e07d212..501ca7589dafde 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/reserved_role_badge.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/reserved_role_badge.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Role } from '../../../../../common/model'; -import { isReservedRole } from '../../../../lib/role_utils'; +import { Role, isReservedRole } from '../../../../common/model'; interface Props { role: Role; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_index.scss b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_index.scss new file mode 100644 index 00000000000000..b40a32cb8df96b --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_index.scss @@ -0,0 +1 @@ +@import './spaces_popover_list'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/_spaces_popover_list.scss b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_spaces_popover_list.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/_spaces_popover_list.scss rename to x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/_spaces_popover_list.scss diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/index.ts rename to x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx similarity index 95% rename from x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx rename to x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx index a99e389044eaad..bb7a6db97f7c88 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx @@ -14,9 +14,9 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { SpaceAvatar } from '../../../../../../../spaces/public/space_avatar'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../../../../plugins/spaces/common/constants'; -import { Space } from '../../../../../../../../../plugins/spaces/common/model/space'; +import { SpaceAvatar } from '../../../../../../../legacy/plugins/spaces/public/space_avatar'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../spaces/common'; +import { Space } from '../../../../../../spaces/common/model/space'; interface Props { spaces: Space[]; @@ -146,7 +146,6 @@ export class SpacesPopoverList extends Component { return (
{ - // @ts-ignore onSearch isn't defined on the type ({ + getFields: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/management/roles/indices_api_client.ts b/x-pack/plugins/security/public/management/roles/indices_api_client.ts new file mode 100644 index 00000000000000..65d9a40a776eb0 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/indices_api_client.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpStart } from 'src/core/public'; + +export class IndicesAPIClient { + constructor(private readonly http: HttpStart) {} + + async getFields(query: string) { + return ( + (await this.http.get(`/internal/security/fields/${encodeURIComponent(query)}`)) || + [] + ); + } +} diff --git a/x-pack/plugins/security/public/management/roles/privileges_api_client.mock.ts b/x-pack/plugins/security/public/management/roles/privileges_api_client.mock.ts new file mode 100644 index 00000000000000..2564914a1d3d83 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/privileges_api_client.mock.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const privilegesAPIClientMock = { + create: () => ({ + getAll: jest.fn(), + getBuiltIn: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/management/roles/privileges_api_client.ts b/x-pack/plugins/security/public/management/roles/privileges_api_client.ts new file mode 100644 index 00000000000000..45bd2fd8fb3a6a --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/privileges_api_client.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpStart } from 'src/core/public'; +import { BuiltinESPrivileges, RawKibanaPrivileges } from '../../../common/model'; + +export class PrivilegesAPIClient { + constructor(private readonly http: HttpStart) {} + + async getAll({ includeActions }: { includeActions: boolean }) { + return await this.http.get('/api/security/privileges', { + query: { includeActions }, + }); + } + + async getBuiltIn() { + return await this.http.get('/internal/security/esPrivileges/builtin'); + } +} diff --git a/x-pack/plugins/security/public/management/roles/roles_api_client.mock.ts b/x-pack/plugins/security/public/management/roles/roles_api_client.mock.ts new file mode 100644 index 00000000000000..c4d3724c0ecb5f --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/roles_api_client.mock.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const rolesAPIClientMock = { + create: () => ({ + getRoles: jest.fn(), + getRole: jest.fn(), + deleteRole: jest.fn(), + saveRole: jest.fn(), + }), +}; diff --git a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.test.ts b/x-pack/plugins/security/public/management/roles/roles_api_client.test.ts similarity index 82% rename from x-pack/legacy/plugins/security/public/lib/transform_role_for_save.test.ts rename to x-pack/plugins/security/public/management/roles/roles_api_client.test.ts index 1ea19f2637305e..75611613684051 100644 --- a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.test.ts +++ b/x-pack/plugins/security/public/management/roles/roles_api_client.test.ts @@ -4,12 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Role } from '../../common/model'; -import { transformRoleForSave } from './transform_role_for_save'; +import { Role } from '../../../common/model'; +import { RolesAPIClient } from './roles_api_client'; +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; + +describe('RolesAPIClient', () => { + async function saveRole(role: Role, spacesEnabled: boolean) { + const httpMock = httpServiceMock.createStartContract(); + const rolesAPIClient = new RolesAPIClient(httpMock); + + await rolesAPIClient.saveRole({ role, spacesEnabled }); + expect(httpMock.put).toHaveBeenCalledTimes(1); + + return JSON.parse(httpMock.put.mock.calls[0][1]?.body as any); + } -describe('transformRoleForSave', () => { describe('spaces disabled', () => { - it('removes placeholder index privileges', () => { + it('removes placeholder index privileges', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -20,7 +31,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -32,7 +43,7 @@ describe('transformRoleForSave', () => { }); }); - it('removes placeholder query entries', () => { + it('removes placeholder query entries', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -43,7 +54,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -55,7 +66,7 @@ describe('transformRoleForSave', () => { }); }); - it('removes transient fields not required for save', () => { + it('removes transient fields not required for save', async () => { const role: Role = { name: 'my role', transient_metadata: { @@ -74,7 +85,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ metadata: { @@ -89,7 +100,7 @@ describe('transformRoleForSave', () => { }); }); - it('does not remove actual query entries', () => { + it('does not remove actual query entries', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -100,7 +111,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -112,7 +123,7 @@ describe('transformRoleForSave', () => { }); }); - it('should remove feature privileges if a corresponding base privilege is defined', () => { + it('should remove feature privileges if a corresponding base privilege is defined', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -132,7 +143,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -150,7 +161,7 @@ describe('transformRoleForSave', () => { }); }); - it('should not remove feature privileges if a corresponding base privilege is not defined', () => { + it('should not remove feature privileges if a corresponding base privilege is not defined', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -170,7 +181,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -191,7 +202,7 @@ describe('transformRoleForSave', () => { }); }); - it('should remove space privileges', () => { + it('should remove space privileges', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -219,7 +230,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, false); + const result = await saveRole(role, false); expect(result).toEqual({ elasticsearch: { @@ -242,7 +253,7 @@ describe('transformRoleForSave', () => { }); describe('spaces enabled', () => { - it('removes placeholder index privileges', () => { + it('removes placeholder index privileges', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -253,7 +264,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { @@ -265,7 +276,7 @@ describe('transformRoleForSave', () => { }); }); - it('removes placeholder query entries', () => { + it('removes placeholder query entries', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -276,7 +287,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { @@ -288,7 +299,7 @@ describe('transformRoleForSave', () => { }); }); - it('removes transient fields not required for save', () => { + it('removes transient fields not required for save', async () => { const role: Role = { name: 'my role', transient_metadata: { @@ -307,7 +318,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ metadata: { @@ -322,7 +333,7 @@ describe('transformRoleForSave', () => { }); }); - it('does not remove actual query entries', () => { + it('does not remove actual query entries', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -333,7 +344,7 @@ describe('transformRoleForSave', () => { kibana: [], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { @@ -345,7 +356,7 @@ describe('transformRoleForSave', () => { }); }); - it('should remove feature privileges if a corresponding base privilege is defined', () => { + it('should remove feature privileges if a corresponding base privilege is defined', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -365,7 +376,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { @@ -383,7 +394,7 @@ describe('transformRoleForSave', () => { }); }); - it('should not remove feature privileges if a corresponding base privilege is not defined', () => { + it('should not remove feature privileges if a corresponding base privilege is not defined', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -403,7 +414,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { @@ -424,7 +435,7 @@ describe('transformRoleForSave', () => { }); }); - it('should not remove space privileges', () => { + it('should not remove space privileges', async () => { const role: Role = { name: 'my role', elasticsearch: { @@ -452,7 +463,7 @@ describe('transformRoleForSave', () => { ], }; - const result = transformRoleForSave(role, true); + const result = await saveRole(role, true); expect(result).toEqual({ elasticsearch: { diff --git a/x-pack/plugins/security/public/management/roles/roles_api_client.ts b/x-pack/plugins/security/public/management/roles/roles_api_client.ts new file mode 100644 index 00000000000000..d7e98e03a965b7 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/roles_api_client.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpStart } from 'src/core/public'; +import { Role, RoleIndexPrivilege, copyRole } from '../../../common/model'; +import { isGlobalPrivilegeDefinition } from './edit_role/privilege_utils'; + +export class RolesAPIClient { + constructor(private readonly http: HttpStart) {} + + public async getRoles() { + return await this.http.get('/api/security/role'); + } + + public async getRole(roleName: string) { + return await this.http.get(`/api/security/role/${encodeURIComponent(roleName)}`); + } + + public async deleteRole(roleName: string) { + await this.http.delete(`/api/security/role/${encodeURIComponent(roleName)}`); + } + + public async saveRole({ role, spacesEnabled }: { role: Role; spacesEnabled: boolean }) { + await this.http.put(`/api/security/role/${encodeURIComponent(role.name)}`, { + body: JSON.stringify(this.transformRoleForSave(copyRole(role), spacesEnabled)), + }); + } + + private transformRoleForSave(role: Role, spacesEnabled: boolean) { + // Remove any placeholder index privileges + const isPlaceholderPrivilege = (indexPrivilege: RoleIndexPrivilege) => + indexPrivilege.names.length === 0; + role.elasticsearch.indices = role.elasticsearch.indices.filter( + indexPrivilege => !isPlaceholderPrivilege(indexPrivilege) + ); + + // Remove any placeholder query entries + role.elasticsearch.indices.forEach(index => index.query || delete index.query); + + // If spaces are disabled, then do not persist any space privileges + if (!spacesEnabled) { + role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition); + } + + role.kibana.forEach(kibanaPrivilege => { + // If a base privilege is defined, then do not persist feature privileges + if (kibanaPrivilege.base.length > 0) { + kibanaPrivilege.feature = {}; + } + }); + + delete role.name; + delete role.transient_metadata; + delete role._unrecognized_applications; + delete role._transform_error; + + return role; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/__snapshots__/roles_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/__snapshots__/roles_grid_page.test.tsx.snap rename to x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/confirm_delete/confirm_delete.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx similarity index 72% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/confirm_delete/confirm_delete.tsx rename to x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx index 34784b4b2accb6..37eed3357241d5 100644 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/confirm_delete/confirm_delete.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { Component, Fragment } from 'react'; import { EuiButton, EuiButtonEmpty, @@ -15,23 +16,24 @@ import { EuiOverlayMask, EuiText, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import React, { Component, Fragment } from 'react'; -import { toastNotifications } from 'ui/notify'; -import { RolesApi } from '../../../../../lib/roles_api'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsStart } from 'src/core/public'; +import { RolesAPIClient } from '../../roles_api_client'; interface Props { rolesToDelete: string[]; - intl: InjectedIntl; callback: (rolesToDelete: string[], errors: string[]) => void; onCancel: () => void; + notifications: NotificationsStart; + rolesAPIClient: PublicMethodsOf; } interface State { deleteInProgress: boolean; } -class ConfirmDeleteUI extends Component { +export class ConfirmDelete extends Component { constructor(props: Props) { super(props); this.state = { @@ -40,15 +42,12 @@ class ConfirmDeleteUI extends Component { } public render() { - const { rolesToDelete, intl } = this.props; + const { rolesToDelete } = this.props; const moreThanOne = rolesToDelete.length > 1; - const title = intl.formatMessage( - { - id: 'xpack.security.management.roles.deleteRoleTitle', - defaultMessage: 'Delete role{value, plural, one {{roleName}} other {s}}', - }, - { value: rolesToDelete.length, roleName: ` ${rolesToDelete[0]}` } - ); + const title = i18n.translate('xpack.security.management.roles.deleteRoleTitle', { + defaultMessage: 'Delete role{value, plural, one {{roleName}} other {s}}', + values: { value: rolesToDelete.length, roleName: ` ${rolesToDelete[0]}` }, + }); // This is largely the same as the built-in EuiConfirmModal component, but we needed the ability // to disable the buttons since this could be a long-running operation @@ -128,32 +127,24 @@ class ConfirmDeleteUI extends Component { }; private deleteRoles = async () => { - const { rolesToDelete, callback } = this.props; + const { rolesToDelete, callback, rolesAPIClient, notifications } = this.props; const errors: string[] = []; const deleteOperations = rolesToDelete.map(roleName => { const deleteRoleTask = async () => { try { - await RolesApi.deleteRole(roleName); - toastNotifications.addSuccess( - this.props.intl.formatMessage( - { - id: - 'xpack.security.management.roles.confirmDelete.roleSuccessfullyDeletedNotificationMessage', - defaultMessage: 'Deleted role {roleName}', - }, - { roleName } + await rolesAPIClient.deleteRole(roleName); + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.security.management.roles.confirmDelete.roleSuccessfullyDeletedNotificationMessage', + { defaultMessage: 'Deleted role {roleName}', values: { roleName } } ) ); } catch (e) { errors.push(roleName); - toastNotifications.addDanger( - this.props.intl.formatMessage( - { - id: - 'xpack.security.management.roles.confirmDelete.roleDeletingErrorNotificationMessage', - defaultMessage: 'Error deleting role {roleName}', - }, - { roleName } + notifications.toasts.addDanger( + i18n.translate( + 'xpack.security.management.roles.confirmDelete.roleDeletingErrorNotificationMessage', + { defaultMessage: 'Error deleting role {roleName}', values: { roleName } } ) ); } @@ -167,5 +158,3 @@ class ConfirmDeleteUI extends Component { callback(rolesToDelete, errors); }; } - -export const ConfirmDelete = injectI18n(ConfirmDeleteUI); diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/confirm_delete/index.ts b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/confirm_delete/index.ts rename to x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/index.ts b/x-pack/plugins/security/public/management/roles/roles_grid/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/index.ts rename to x-pack/plugins/security/public/management/roles/roles_grid/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/permission_denied/index.ts b/x-pack/plugins/security/public/management/roles/roles_grid/permission_denied/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/permission_denied/index.ts rename to x-pack/plugins/security/public/management/roles/roles_grid/permission_denied/index.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/permission_denied/permission_denied.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/permission_denied/permission_denied.tsx similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/permission_denied/permission_denied.tsx rename to x-pack/plugins/security/public/management/roles/roles_grid/permission_denied/permission_denied.tsx diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx similarity index 63% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.test.tsx rename to x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index 6da2f2442d488b..63ace53420612d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -4,50 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -let mockSimulate403 = false; -const mock403 = () => ({ body: { statusCode: 403 } }); -jest.mock('../../../../lib/roles_api', () => { - return { - RolesApi: { - async getRoles() { - if (mockSimulate403) { - throw mock403(); - } - return [ - { - name: 'test-role-1', - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: { global: [], space: {} }, - }, - { - name: 'reserved-role', - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: { global: [], space: {} }, - metadata: { - _reserved: true, - }, - }, - { - name: 'disabled-role', - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: { global: [], space: {} }, - transient_metadata: { - enabled: false, - }, - }, - ]; - }, - }, - }; -}); - import { EuiIcon } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { RolesAPIClient } from '../roles_api_client'; import { PermissionDenied } from './permission_denied'; import { RolesGridPage } from './roles_grid_page'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { rolesAPIClientMock } from '../index.mock'; + +const mock403 = () => ({ body: { statusCode: 403 } }); + const waitForRender = async ( wrapper: ReactWrapper, condition: (wrapper: ReactWrapper) => boolean @@ -69,12 +38,37 @@ const waitForRender = async ( }; describe('', () => { + let apiClientMock: jest.Mocked>; beforeEach(() => { - mockSimulate403 = false; + apiClientMock = rolesAPIClientMock.create(); + apiClientMock.getRoles.mockResolvedValue([ + { + name: 'test-role-1', + elasticsearch: { cluster: [], indices: [], run_as: [] }, + kibana: [{ base: [], spaces: [], feature: {} }], + }, + { + name: 'reserved-role', + elasticsearch: { cluster: [], indices: [], run_as: [] }, + kibana: [{ base: [], spaces: [], feature: {} }], + metadata: { _reserved: true }, + }, + { + name: 'disabled-role', + elasticsearch: { cluster: [], indices: [], run_as: [] }, + kibana: [{ base: [], spaces: [], feature: {} }], + transient_metadata: { enabled: false }, + }, + ]); }); it(`renders reserved roles as such`, async () => { - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); const initialIconCount = wrapper.find(EuiIcon).length; await waitForRender(wrapper, updatedWrapper => { @@ -87,8 +81,14 @@ describe('', () => { }); it('renders permission denied if required', async () => { - mockSimulate403 = true; - const wrapper = mountWithIntl(); + apiClientMock.getRoles.mockRejectedValue(mock403()); + + const wrapper = mountWithIntl( + + ); await waitForRender(wrapper, updatedWrapper => { return updatedWrapper.find(PermissionDenied).length > 0; }); @@ -96,7 +96,12 @@ describe('', () => { }); it('renders role actions as appropriate', async () => { - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); const initialIconCount = wrapper.find(EuiIcon).length; await waitForRender(wrapper, updatedWrapper => { diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx similarity index 78% rename from x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.tsx rename to x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 2083a93f4b33c9..7c686bef391a79 100644 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/components/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; +import React, { Component } from 'react'; import { EuiButton, EuiIcon, @@ -18,18 +20,17 @@ import { EuiButtonIcon, EuiBasicTableColumn, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import _ from 'lodash'; -import React, { Component } from 'react'; -import { toastNotifications } from 'ui/notify'; -import { Role } from '../../../../../common/model'; -import { isRoleEnabled, isReadOnlyRole, isReservedRole } from '../../../../lib/role_utils'; -import { RolesApi } from '../../../../lib/roles_api'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsStart } from 'src/core/public'; +import { Role, isRoleEnabled, isReadOnlyRole, isReservedRole } from '../../../../common/model'; +import { RolesAPIClient } from '../roles_api_client'; import { ConfirmDelete } from './confirm_delete'; import { PermissionDenied } from './permission_denied'; interface Props { - intl: InjectedIntl; + notifications: NotificationsStart; + rolesAPIClient: PublicMethodsOf; } interface State { @@ -44,7 +45,7 @@ const getRoleManagementHref = (action: 'edit' | 'clone', roleName?: string) => { return `#/management/security/roles/${action}${roleName ? `/${roleName}` : ''}`; }; -class RolesGridPageUI extends Component { +export class RolesGridPage extends Component { constructor(props: Props) { super(props); this.state = { @@ -68,7 +69,6 @@ class RolesGridPageUI extends Component { private getPageContent = () => { const { roles } = this.state; - const { intl } = this.props; return ( @@ -105,6 +105,8 @@ class RolesGridPageUI extends Component { onCancel={this.onCancelDelete} rolesToDelete={this.state.selection.map(role => role.name)} callback={this.handleDelete} + notifications={this.props.notifications} + rolesAPIClient={this.props.rolesAPIClient} /> ) : null} @@ -112,7 +114,7 @@ class RolesGridPageUI extends Component { !role.metadata || !role.metadata._reserved, @@ -155,17 +157,16 @@ class RolesGridPageUI extends Component { ); }; - private getColumnConfig = (intl: InjectedIntl) => { - const reservedRoleDesc = intl.formatMessage({ - id: 'xpack.security.management.roles.reservedColumnDescription', - defaultMessage: 'Reserved roles are built-in and cannot be edited or removed.', - }); + private getColumnConfig = () => { + const reservedRoleDesc = i18n.translate( + 'xpack.security.management.roles.reservedColumnDescription', + { defaultMessage: 'Reserved roles are built-in and cannot be edited or removed.' } + ); return [ { field: 'name', - name: intl.formatMessage({ - id: 'xpack.security.management.roles.nameColumnName', + name: i18n.translate('xpack.security.management.roles.nameColumnName', { defaultMessage: 'Role', }), sortable: true, @@ -188,8 +189,7 @@ class RolesGridPageUI extends Component { }, { field: 'metadata', - name: intl.formatMessage({ - id: 'xpack.security.management.roles.reservedColumnName', + name: i18n.translate('xpack.security.management.roles.reservedColumnName', { defaultMessage: 'Reserved', }), sortable: ({ metadata }: Role) => Boolean(metadata && metadata._reserved), @@ -197,8 +197,7 @@ class RolesGridPageUI extends Component { align: 'right', description: reservedRoleDesc, render: (metadata: Role['metadata']) => { - const label = intl.formatMessage({ - id: 'xpack.security.management.roles.reservedRoleIconLabel', + const label = i18n.translate('xpack.security.management.roles.reservedRoleIconLabel', { defaultMessage: 'Reserved role', }); @@ -210,8 +209,7 @@ class RolesGridPageUI extends Component { }, }, { - name: intl.formatMessage({ - id: 'xpack.security.management.roles.actionsColumnName', + name: i18n.translate('xpack.security.management.roles.actionsColumnName', { defaultMessage: 'Actions', }), width: '150px', @@ -219,15 +217,10 @@ class RolesGridPageUI extends Component { { available: (role: Role) => !isReadOnlyRole(role), render: (role: Role) => { - const title = intl.formatMessage( - { - id: 'xpack.security.management.roles.editRoleActionName', - defaultMessage: `Edit {roleName}`, - }, - { - roleName: role.name, - } - ); + const title = i18n.translate('xpack.security.management.roles.editRoleActionName', { + defaultMessage: `Edit {roleName}`, + values: { roleName: role.name }, + }); return ( { { available: (role: Role) => !isReservedRole(role), render: (role: Role) => { - const title = intl.formatMessage( - { - id: 'xpack.security.management.roles.cloneRoleActionName', - defaultMessage: `Clone {roleName}`, - }, - { - roleName: role.name, - } - ); + const title = i18n.translate('xpack.security.management.roles.cloneRoleActionName', { + defaultMessage: `Clone {roleName}`, + values: { roleName: role.name }, + }); return ( { private async loadRoles() { try { - const roles = await RolesApi.getRoles(); + const roles = await this.props.rolesAPIClient.getRoles(); this.setState({ roles }); } catch (e) { if (_.get(e, 'body.statusCode') === 403) { this.setState({ permissionDenied: true }); } else { - toastNotifications.addDanger( - this.props.intl.formatMessage( - { - id: 'xpack.security.management.roles.fetchingRolesErrorMessage', - defaultMessage: 'Error fetching roles: {message}', - }, - { message: _.get(e, 'body.message', '') } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.security.management.roles.fetchingRolesErrorMessage', { + defaultMessage: 'Error fetching roles: {message}', + values: { message: _.get(e, 'body.message', '') }, + }) ); } } @@ -339,5 +324,3 @@ class RolesGridPageUI extends Component { this.setState({ showDeleteConfirmation: false }); }; } - -export const RolesGridPage = injectI18n(RolesGridPageUI); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx new file mode 100644 index 00000000000000..48bc1a6580a93d --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { licenseMock } from '../../../common/licensing/index.mock'; + +jest.mock('./roles_grid', () => ({ + RolesGridPage: (props: any) => `Roles Page: ${JSON.stringify(props)}`, +})); + +jest.mock('./edit_role', () => ({ + EditRolePage: (props: any) => `Role Edit Page: ${JSON.stringify(props)}`, +})); + +import { rolesManagementApp } from './roles_management_app'; + +import { coreMock } from '../../../../../../src/core/public/mocks'; + +async function mountApp(basePath: string) { + const { fatalErrors } = coreMock.createSetup(); + const container = document.createElement('div'); + const setBreadcrumbs = jest.fn(); + + const unmount = await rolesManagementApp + .create({ + license: licenseMock.create(), + fatalErrors, + getStartServices: jest.fn().mockResolvedValue([coreMock.createStart(), { data: {} }]), + }) + .mount({ basePath, element: container, setBreadcrumbs }); + + return { unmount, container, setBreadcrumbs }; +} + +describe('rolesManagementApp', () => { + it('create() returns proper management app descriptor', () => { + const { fatalErrors, getStartServices } = coreMock.createSetup(); + + expect( + rolesManagementApp.create({ + license: licenseMock.create(), + fatalErrors, + getStartServices: getStartServices as any, + }) + ).toMatchInlineSnapshot(` + Object { + "id": "roles", + "mount": [Function], + "order": 20, + "title": "Roles", + } + `); + }); + + it('mount() works for the `grid` page', async () => { + const basePath = '/some-base-path/roles'; + window.location.hash = basePath; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Roles' }]); + expect(container).toMatchInlineSnapshot(` +
+ Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `create role` page', async () => { + const basePath = '/some-base-path/roles'; + window.location.hash = `${basePath}/edit`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Roles' }, + { text: 'Create' }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `edit role` page', async () => { + const basePath = '/some-base-path/roles'; + const roleName = 'someRoleName'; + window.location.hash = `${basePath}/edit/${roleName}`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Roles' }, + { href: `#/some-base-path/roles/edit/${roleName}`, text: roleName }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `clone role` page', async () => { + const basePath = '/some-base-path/roles'; + const roleName = 'someRoleName'; + window.location.hash = `${basePath}/clone/${roleName}`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Roles' }, + { text: 'Create' }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() properly encodes role name in `edit role` page link in breadcrumbs', async () => { + const basePath = '/some-base-path/roles'; + const roleName = 'some 安全性 role'; + window.location.hash = `${basePath}/edit/${roleName}`; + + const { setBreadcrumbs } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Roles' }, + { + href: '#/some-base-path/roles/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', + text: roleName, + }, + ]); + }); +}); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx new file mode 100644 index 00000000000000..4e8c95b61c2f19 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, FatalErrorsSetup } from 'src/core/public'; +import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import { SecurityLicense } from '../../../common/licensing'; +import { PluginStartDependencies } from '../../plugin'; +import { UserAPIClient } from '../users'; +import { RolesAPIClient } from './roles_api_client'; +import { RolesGridPage } from './roles_grid'; +import { EditRolePage } from './edit_role'; +import { DocumentationLinksService } from './documentation_links'; +import { IndicesAPIClient } from './indices_api_client'; +import { PrivilegesAPIClient } from './privileges_api_client'; + +interface CreateParams { + fatalErrors: FatalErrorsSetup; + license: SecurityLicense; + getStartServices: CoreSetup['getStartServices']; +} + +export const rolesManagementApp = Object.freeze({ + id: 'roles', + create({ license, fatalErrors, getStartServices }: CreateParams) { + return { + id: this.id, + order: 20, + title: i18n.translate('xpack.security.management.rolesTitle', { defaultMessage: 'Roles' }), + async mount({ basePath, element, setBreadcrumbs }) { + const [ + { application, docLinks, http, i18n: i18nStart, injectedMetadata, notifications }, + { data }, + ] = await getStartServices(); + + const rolesBreadcrumbs = [ + { + text: i18n.translate('xpack.security.roles.breadcrumb', { defaultMessage: 'Roles' }), + href: `#${basePath}`, + }, + ]; + + const rolesAPIClient = new RolesAPIClient(http); + const RolesGridPageWithBreadcrumbs = () => { + setBreadcrumbs(rolesBreadcrumbs); + return ; + }; + + const EditRolePageWithBreadcrumbs = ({ action }: { action: 'edit' | 'clone' }) => { + const { roleName } = useParams<{ roleName?: string }>(); + + setBreadcrumbs([ + ...rolesBreadcrumbs, + action === 'edit' && roleName + ? { text: roleName, href: `#${basePath}/edit/${encodeURIComponent(roleName)}` } + : { + text: i18n.translate('xpack.security.roles.createBreadcrumb', { + defaultMessage: 'Create', + }), + }, + ]); + + return ( + + ); + }; + + render( + + + + + + + + + + + + + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; + }, + } as RegisterManagementAppArgs; + }, +}); diff --git a/x-pack/plugins/security/public/management/users/_index.scss b/x-pack/plugins/security/public/management/users/_index.scss new file mode 100644 index 00000000000000..35df0c1b965837 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/_index.scss @@ -0,0 +1 @@ +@import './edit_user/index'; diff --git a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.test.tsx b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx similarity index 82% rename from x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.test.tsx rename to x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx index 221120532318cb..be46612767a593 100644 --- a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.test.tsx +++ b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx @@ -7,10 +7,12 @@ import { EuiFieldText } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { User } from '../../../../common/model'; -import { UserAPIClient } from '../../../lib/api'; +import { User } from '../../../../../common/model'; import { ChangePasswordForm } from './change_password_form'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { userAPIClientMock } from '../../index.mock'; + function getCurrentPasswordField(wrapper: ReactWrapper) { return wrapper.find(EuiFieldText).filter('[data-test-subj="currentPassword"]'); } @@ -23,8 +25,6 @@ function getConfirmPasswordField(wrapper: ReactWrapper) { return wrapper.find(EuiFieldText).filter('[data-test-subj="confirmNewPassword"]'); } -jest.mock('ui/kfetch'); - describe('', () => { describe('for the current user', () => { it('shows fields for current and new passwords', () => { @@ -40,7 +40,8 @@ describe('', () => { ); @@ -60,15 +61,15 @@ describe('', () => { const callback = jest.fn(); - const apiClient = new UserAPIClient(); - apiClient.changePassword = jest.fn(); + const apiClientMock = userAPIClientMock.create(); const wrapper = mountWithIntl( ); @@ -83,8 +84,8 @@ describe('', () => { wrapper.find('button[data-test-subj="changePasswordButton"]').simulate('click'); - expect(apiClient.changePassword).toHaveBeenCalledTimes(1); - expect(apiClient.changePassword).toHaveBeenCalledWith( + expect(apiClientMock.changePassword).toHaveBeenCalledTimes(1); + expect(apiClientMock.changePassword).toHaveBeenCalledWith( 'user', 'myNewPassword', 'myCurrentPassword' @@ -106,7 +107,8 @@ describe('', () => { ); diff --git a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx similarity index 96% rename from x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx rename to x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx index 61c0b77decd565..6dcf330ec6f9e1 100644 --- a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx +++ b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.tsx @@ -5,10 +5,7 @@ */ import { EuiButton, - // @ts-ignore EuiButtonEmpty, - // @ts-ignore - EuiDescribedFormGroup, EuiFieldText, EuiFlexGroup, EuiFlexItem, @@ -18,15 +15,16 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { ChangeEvent, Component } from 'react'; -import { toastNotifications } from 'ui/notify'; -import { User } from '../../../../common/model'; -import { UserAPIClient } from '../../../lib/api'; +import { NotificationsStart } from 'src/core/public'; +import { User } from '../../../../../common/model'; +import { UserAPIClient } from '../..'; interface Props { user: User; isUserChangingOwnPassword: boolean; onChangePassword?: () => void; - apiClient: UserAPIClient; + apiClient: PublicMethodsOf; + notifications: NotificationsStart; } interface State { @@ -294,7 +292,7 @@ export class ChangePasswordForm extends Component { }; private handleChangePasswordSuccess = () => { - toastNotifications.addSuccess({ + this.props.notifications.toasts.addSuccess({ title: i18n.translate('xpack.security.account.changePasswordSuccess', { defaultMessage: 'Your password has been changed.', }), @@ -317,7 +315,7 @@ export class ChangePasswordForm extends Component { if (error.body && error.body.statusCode === 403) { this.setState({ currentPasswordError: true }); } else { - toastNotifications.addDanger( + this.props.notifications.toasts.addDanger( i18n.translate('xpack.security.management.users.editUser.settingPasswordErrorMessage', { defaultMessage: 'Error setting password: {message}', values: { message: _.get(error, 'body.message') }, diff --git a/x-pack/legacy/plugins/security/public/components/management/change_password_form/index.ts b/x-pack/plugins/security/public/management/users/components/change_password_form/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/components/management/change_password_form/index.ts rename to x-pack/plugins/security/public/management/users/components/change_password_form/index.ts diff --git a/x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.test.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx similarity index 58% rename from x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.test.tsx rename to x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx index 9f69fc7a7551f0..bcec707b03f936 100644 --- a/x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.test.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx @@ -5,16 +5,21 @@ */ import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { ConfirmDeleteUsers } from './confirm_delete'; +import { ConfirmDeleteUsers } from './confirm_delete_users'; import React from 'react'; -import { UserAPIClient } from '../../../lib/api'; -jest.mock('ui/kfetch'); +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { userAPIClientMock } from '../../index.mock'; describe('ConfirmDeleteUsers', () => { it('renders a warning for a single user', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.find('EuiModalHeaderTitle').text()).toMatchInlineSnapshot(`"Delete user foo"`); @@ -23,7 +28,8 @@ describe('ConfirmDeleteUsers', () => { it('renders a warning for a multiple users', () => { const wrapper = mountWithIntl( @@ -35,7 +41,12 @@ describe('ConfirmDeleteUsers', () => { it('fires onCancel when the operation is cancelled', () => { const onCancel = jest.fn(); const wrapper = mountWithIntl( - + ); expect(onCancel).toBeCalledTimes(0); @@ -47,50 +58,48 @@ describe('ConfirmDeleteUsers', () => { it('deletes the requested users when confirmed', () => { const onCancel = jest.fn(); - const deleteUser = jest.fn(); - - const apiClient = new UserAPIClient(); - apiClient.deleteUser = deleteUser; + const apiClientMock = userAPIClientMock.create(); const wrapper = mountWithIntl( ); wrapper.find('EuiButton[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(deleteUser).toBeCalledTimes(2); - expect(deleteUser).toBeCalledWith('foo'); - expect(deleteUser).toBeCalledWith('bar'); + expect(apiClientMock.deleteUser).toBeCalledTimes(2); + expect(apiClientMock.deleteUser).toBeCalledWith('foo'); + expect(apiClientMock.deleteUser).toBeCalledWith('bar'); }); it('attempts to delete all users even if some fail', () => { const onCancel = jest.fn(); - const deleteUser = jest.fn().mockImplementation(user => { + + const apiClientMock = userAPIClientMock.create(); + apiClientMock.deleteUser.mockImplementation(user => { if (user === 'foo') { return Promise.reject('something terrible happened'); } return Promise.resolve(); }); - const apiClient = new UserAPIClient(); - apiClient.deleteUser = deleteUser; - const wrapper = mountWithIntl( ); wrapper.find('EuiButton[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(deleteUser).toBeCalledTimes(2); - expect(deleteUser).toBeCalledWith('foo'); - expect(deleteUser).toBeCalledWith('bar'); + expect(apiClientMock.deleteUser).toBeCalledTimes(2); + expect(apiClientMock.deleteUser).toBeCalledWith('foo'); + expect(apiClientMock.deleteUser).toBeCalledWith('bar'); }); }); diff --git a/x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx similarity index 50% rename from x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.tsx rename to x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx index 53bb022afb513f..b7269e0168d7d5 100644 --- a/x-pack/legacy/plugins/security/public/components/management/users/confirm_delete.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx @@ -6,51 +6,46 @@ import React, { Component, Fragment } from 'react'; import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; -import { toastNotifications } from 'ui/notify'; -import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react'; -import { UserAPIClient } from '../../../lib/api'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsStart } from 'src/core/public'; +import { UserAPIClient } from '../..'; interface Props { - intl: InjectedIntl; usersToDelete: string[]; - apiClient: UserAPIClient; + apiClient: PublicMethodsOf; + notifications: NotificationsStart; onCancel: () => void; callback?: (usersToDelete: string[], errors: string[]) => void; } -class ConfirmDeleteUI extends Component { +export class ConfirmDeleteUsers extends Component { public render() { - const { usersToDelete, onCancel, intl } = this.props; + const { usersToDelete, onCancel } = this.props; const moreThanOne = usersToDelete.length > 1; const title = moreThanOne - ? intl.formatMessage( - { - id: 'xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle', - defaultMessage: 'Delete {userLength} users', - }, - { userLength: usersToDelete.length } - ) - : intl.formatMessage( - { - id: 'xpack.security.management.users.confirmDelete.deleteOneUserTitle', - defaultMessage: 'Delete user {userLength}', - }, - { userLength: usersToDelete[0] } - ); + ? i18n.translate('xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle', { + defaultMessage: 'Delete {userLength} users', + values: { userLength: usersToDelete.length }, + }) + : i18n.translate('xpack.security.management.users.confirmDelete.deleteOneUserTitle', { + defaultMessage: 'Delete user {userLength}', + values: { userLength: usersToDelete[0] }, + }); return (
@@ -82,31 +77,23 @@ class ConfirmDeleteUI extends Component { } private deleteUsers = () => { - const { usersToDelete, callback, apiClient } = this.props; + const { usersToDelete, callback, apiClient, notifications } = this.props; const errors: string[] = []; usersToDelete.forEach(async username => { try { await apiClient.deleteUser(username); - toastNotifications.addSuccess( - this.props.intl.formatMessage( - { - id: - 'xpack.security.management.users.confirmDelete.userSuccessfullyDeletedNotificationMessage', - defaultMessage: 'Deleted user {username}', - }, - { username } + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.security.management.users.confirmDelete.userSuccessfullyDeletedNotificationMessage', + { defaultMessage: 'Deleted user {username}', values: { username } } ) ); } catch (e) { errors.push(username); - toastNotifications.addDanger( - this.props.intl.formatMessage( - { - id: - 'xpack.security.management.users.confirmDelete.userDeletingErrorNotificationMessage', - defaultMessage: 'Error deleting user {username}', - }, - { username } + notifications.toasts.addDanger( + i18n.translate( + 'xpack.security.management.users.confirmDelete.userDeletingErrorNotificationMessage', + { defaultMessage: 'Error deleting user {username}', values: { username } } ) ); } @@ -116,5 +103,3 @@ class ConfirmDeleteUI extends Component { }); }; } - -export const ConfirmDeleteUsers = injectI18n(ConfirmDeleteUI); diff --git a/x-pack/legacy/plugins/security/public/components/management/users/index.ts b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/index.ts similarity index 79% rename from x-pack/legacy/plugins/security/public/components/management/users/index.ts rename to x-pack/plugins/security/public/management/users/components/confirm_delete_users/index.ts index 813e671c05ccf2..fde35ab0f0d02e 100644 --- a/x-pack/legacy/plugins/security/public/components/management/users/index.ts +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ConfirmDeleteUsers } from './confirm_delete'; +export { ConfirmDeleteUsers } from './confirm_delete_users'; diff --git a/x-pack/plugins/security/public/management/users/components/index.ts b/x-pack/plugins/security/public/management/users/components/index.ts new file mode 100644 index 00000000000000..54011a6a24cbd2 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/components/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ChangePasswordForm } from './change_password_form'; +export { ConfirmDeleteUsers } from './confirm_delete_users'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/_users.scss b/x-pack/plugins/security/public/management/users/edit_user/_edit_user_page.scss similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_user/_users.scss rename to x-pack/plugins/security/public/management/users/edit_user/_edit_user_page.scss diff --git a/x-pack/plugins/security/public/management/users/edit_user/_index.scss b/x-pack/plugins/security/public/management/users/edit_user/_index.scss new file mode 100644 index 00000000000000..734ba7882ba72c --- /dev/null +++ b/x-pack/plugins/security/public/management/users/edit_user/_index.scss @@ -0,0 +1 @@ +@import './edit_user_page'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx similarity index 70% rename from x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx rename to x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index 639646ce48e224..543d20bb92afe8 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -8,13 +8,14 @@ import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; -import { securityMock } from '../../../../../../../../plugins/security/public/mocks'; -import { UserAPIClient } from '../../../../lib/api'; -import { User, Role } from '../../../../../common/model'; +import { User, Role } from '../../../../common/model'; import { ReactWrapper } from 'enzyme'; -import { mockAuthenticatedUser } from '../../../../../../../../plugins/security/common/model/authenticated_user.mock'; -jest.mock('ui/kfetch'); +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../../mocks'; +import { rolesAPIClientMock } from '../../roles/index.mock'; +import { userAPIClientMock } from '../index.mock'; const createUser = (username: string) => { const user: User = { @@ -34,14 +35,12 @@ const createUser = (username: string) => { return user; }; -const buildClient = () => { - const apiClient = new UserAPIClient(); +const buildClients = () => { + const apiClient = userAPIClientMock.create(); + apiClient.getUser.mockImplementation(async (username: string) => createUser(username)); - apiClient.getUser = jest - .fn() - .mockImplementation(async (username: string) => createUser(username)); - - apiClient.getRoles = jest.fn().mockImplementation(() => { + const rolesAPIClient = rolesAPIClientMock.create(); + rolesAPIClient.getRoles.mockImplementation(() => { return Promise.resolve([ { name: 'role 1', @@ -64,7 +63,7 @@ const buildClient = () => { ] as Role[]); }); - return apiClient; + return { apiClient, rolesAPIClient }; }; function buildSecuritySetup() { @@ -85,15 +84,15 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { describe('EditUserPage', () => { it('allows reserved users to be viewed', async () => { - const apiClient = buildClient(); + const { apiClient, rolesAPIClient } = buildClients(); const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( - path} - intl={null as any} + rolesAPIClient={rolesAPIClient} + authc={securitySetup.authc} + notifications={coreMock.createStart().notifications} /> ); @@ -106,15 +105,15 @@ describe('EditUserPage', () => { }); it('allows new users to be created', async () => { - const apiClient = buildClient(); + const { apiClient, rolesAPIClient } = buildClients(); const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( - path} - intl={null as any} + rolesAPIClient={rolesAPIClient} + authc={securitySetup.authc} + notifications={coreMock.createStart().notifications} /> ); @@ -127,15 +126,15 @@ describe('EditUserPage', () => { }); it('allows existing users to be edited', async () => { - const apiClient = buildClient(); + const { apiClient, rolesAPIClient } = buildClients(); const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( - path} - intl={null as any} + rolesAPIClient={rolesAPIClient} + authc={securitySetup.authc} + notifications={coreMock.createStart().notifications} /> ); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx similarity index 77% rename from x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx rename to x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx index bbffe07485f8dc..576f3ff9e6008c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx @@ -26,22 +26,23 @@ import { EuiHorizontalRule, EuiSpacer, } from '@elastic/eui'; -import { toastNotifications } from 'ui/notify'; -import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react'; -import { SecurityPluginSetup } from '../../../../../../../../plugins/security/public'; -import { UserValidator, UserValidationResult } from '../../../../lib/validate_user'; -import { User, EditUser, Role } from '../../../../../common/model'; -import { USERS_PATH } from '../../../../views/management/management_urls'; -import { ConfirmDeleteUsers } from '../../../../components/management/users'; -import { UserAPIClient } from '../../../../lib/api'; -import { ChangePasswordForm } from '../../../../components/management/change_password_form'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsStart } from 'src/core/public'; +import { User, EditUser, Role } from '../../../../common/model'; +import { AuthenticationServiceSetup } from '../../../authentication'; +import { USERS_PATH } from '../../management_urls'; +import { RolesAPIClient } from '../../roles'; +import { ConfirmDeleteUsers, ChangePasswordForm } from '../components'; +import { UserValidator, UserValidationResult } from './validate_user'; +import { UserAPIClient } from '..'; interface Props { - username: string; - intl: InjectedIntl; - changeUrl: (path: string) => void; - apiClient: UserAPIClient; - securitySetup: SecurityPluginSetup; + username?: string; + apiClient: PublicMethodsOf; + rolesAPIClient: PublicMethodsOf; + authc: AuthenticationServiceSetup; + notifications: NotificationsStart; } interface State { @@ -56,7 +57,11 @@ interface State { formError: UserValidationResult | null; } -class EditUserPageUI extends Component { +function backToUserList() { + window.location.hash = USERS_PATH; +} + +export class EditUserPage extends Component { private validator: UserValidator; constructor(props: Props) { @@ -84,7 +89,17 @@ class EditUserPageUI extends Component { } public async componentDidMount() { - const { username, apiClient, securitySetup } = this.props; + await this.setCurrentUser(); + } + + public async componentDidUpdate(prevProps: Props) { + if (prevProps.username !== this.props.username) { + await this.setCurrentUser(); + } + } + + private async setCurrentUser() { + const { username, apiClient, rolesAPIClient, notifications, authc } = this.props; let { user, currentUser } = this.state; if (username) { try { @@ -93,26 +108,24 @@ class EditUserPageUI extends Component { password: '', confirmPassword: '', }; - currentUser = await securitySetup.authc.getCurrentUser(); + currentUser = await authc.getCurrentUser(); } catch (err) { - toastNotifications.addDanger({ - title: this.props.intl.formatMessage({ - id: 'xpack.security.management.users.editUser.errorLoadingUserTitle', + notifications.toasts.addDanger({ + title: i18n.translate('xpack.security.management.users.editUser.errorLoadingUserTitle', { defaultMessage: 'Error loading user', }), text: get(err, 'body.message') || err.message, }); - return; + return backToUserList(); } } let roles: Role[] = []; try { - roles = await apiClient.getRoles(); + roles = await rolesAPIClient.getRoles(); } catch (err) { - toastNotifications.addDanger({ - title: this.props.intl.formatMessage({ - id: 'xpack.security.management.users.editUser.errorLoadingRolesTitle', + notifications.toasts.addDanger({ + title: i18n.translate('xpack.security.management.users.editUser.errorLoadingRolesTitle', { defaultMessage: 'Error loading roles', }), text: get(err, 'body.message') || err.message, @@ -131,8 +144,7 @@ class EditUserPageUI extends Component { private handleDelete = (usernames: string[], errors: string[]) => { if (errors.length === 0) { - const { changeUrl } = this.props; - changeUrl(USERS_PATH); + backToUserList(); } }; @@ -148,7 +160,7 @@ class EditUserPageUI extends Component { this.setState({ formError: null, }); - const { changeUrl, apiClient } = this.props; + const { apiClient } = this.props; const { user, isNewUser, selectedRoles } = this.state; const userToSave: EditUser = { ...user }; if (!isNewUser) { @@ -160,26 +172,23 @@ class EditUserPageUI extends Component { }); try { await apiClient.saveUser(userToSave); - toastNotifications.addSuccess( - this.props.intl.formatMessage( + this.props.notifications.toasts.addSuccess( + i18n.translate( + 'xpack.security.management.users.editUser.userSuccessfullySavedNotificationMessage', { - id: - 'xpack.security.management.users.editUser.userSuccessfullySavedNotificationMessage', defaultMessage: 'Saved user {message}', - }, - { message: user.username } + values: { message: user.username }, + } ) ); - changeUrl(USERS_PATH); + + backToUserList(); } catch (e) { - toastNotifications.addDanger( - this.props.intl.formatMessage( - { - id: 'xpack.security.management.users.editUser.savingUserErrorMessage', - defaultMessage: 'Error saving user: {message}', - }, - { message: get(e, 'body.message', 'Unknown error') } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.security.management.users.editUser.savingUserErrorMessage', { + defaultMessage: 'Error saving user: {message}', + values: { message: get(e, 'body.message', 'Unknown error') }, + }) ); } } @@ -189,8 +198,7 @@ class EditUserPageUI extends Component { return ( { /> { {user.username === 'kibana' ? ( @@ -260,6 +268,7 @@ class EditUserPageUI extends Component { isUserChangingOwnPassword={userIsLoggedInUser} onChangePassword={this.toggleChangePasswordForm} apiClient={this.props.apiClient} + notifications={this.props.notifications} /> ); @@ -352,7 +361,6 @@ class EditUserPageUI extends Component { }; public render() { - const { changeUrl, intl } = this.props; const { user, roles, @@ -417,6 +425,7 @@ class EditUserPageUI extends Component { usersToDelete={[user.username]} callback={this.handleDelete} apiClient={this.props.apiClient} + notifications={this.props.notifications} /> ) : null} @@ -425,17 +434,16 @@ class EditUserPageUI extends Component { {...this.validator.validateUsername(this.state.user)} helpText={ !isNewUser && !reserved - ? intl.formatMessage({ - id: - 'xpack.security.management.users.editUser.changingUserNameAfterCreationDescription', - defaultMessage: `Usernames can't be changed after creation.`, - }) + ? i18n.translate( + 'xpack.security.management.users.editUser.changingUserNameAfterCreationDescription', + { defaultMessage: `Usernames can't be changed after creation.` } + ) : null } - label={intl.formatMessage({ - id: 'xpack.security.management.users.editUser.usernameFormRowLabel', - defaultMessage: 'Username', - })} + label={i18n.translate( + 'xpack.security.management.users.editUser.usernameFormRowLabel', + { defaultMessage: 'Username' } + )} > { {reserved ? null : ( { { )} { @@ -513,7 +521,7 @@ class EditUserPageUI extends Component { {reserved && ( - changeUrl(USERS_PATH)}> + { - changeUrl(USERS_PATH)} - > + { ); } } - -export const EditUserPage = injectI18n(EditUserPageUI); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/index.ts b/x-pack/plugins/security/public/management/users/edit_user/index.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/views/management/edit_user/components/index.ts rename to x-pack/plugins/security/public/management/users/edit_user/index.ts diff --git a/x-pack/legacy/plugins/security/public/lib/validate_user.test.ts b/x-pack/plugins/security/public/management/users/edit_user/validate_user.test.ts similarity index 98% rename from x-pack/legacy/plugins/security/public/lib/validate_user.test.ts rename to x-pack/plugins/security/public/management/users/edit_user/validate_user.test.ts index 0535248fede88d..6050e1868a759a 100644 --- a/x-pack/legacy/plugins/security/public/lib/validate_user.test.ts +++ b/x-pack/plugins/security/public/management/users/edit_user/validate_user.test.ts @@ -5,7 +5,7 @@ */ import { UserValidator, UserValidationResult } from './validate_user'; -import { User, EditUser } from '../../common/model'; +import { User, EditUser } from '../../../../common/model'; function expectValid(result: UserValidationResult) { expect(result.isInvalid).toBe(false); diff --git a/x-pack/legacy/plugins/security/public/lib/validate_user.ts b/x-pack/plugins/security/public/management/users/edit_user/validate_user.ts similarity index 98% rename from x-pack/legacy/plugins/security/public/lib/validate_user.ts rename to x-pack/plugins/security/public/management/users/edit_user/validate_user.ts index 113aaacdcbf966..5edd96c68bf0dc 100644 --- a/x-pack/legacy/plugins/security/public/lib/validate_user.ts +++ b/x-pack/plugins/security/public/management/users/edit_user/validate_user.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { User, EditUser } from '../../common/model'; +import { User, EditUser } from '../../../../common/model'; interface UserValidatorOptions { shouldValidate?: boolean; diff --git a/x-pack/legacy/plugins/security/public/views/management/index.js b/x-pack/plugins/security/public/management/users/index.mock.ts similarity index 80% rename from x-pack/legacy/plugins/security/public/views/management/index.js rename to x-pack/plugins/security/public/management/users/index.mock.ts index 0ed6fe09ef80a4..f090f88da500de 100644 --- a/x-pack/legacy/plugins/security/public/views/management/index.js +++ b/x-pack/plugins/security/public/management/users/index.mock.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './management'; +export { userAPIClientMock } from './user_api_client.mock'; diff --git a/x-pack/legacy/plugins/security/public/objects/index.ts b/x-pack/plugins/security/public/management/users/index.ts similarity index 68% rename from x-pack/legacy/plugins/security/public/objects/index.ts rename to x-pack/plugins/security/public/management/users/index.ts index a6238ca879901c..c8b4d41973da6a 100644 --- a/x-pack/legacy/plugins/security/public/objects/index.ts +++ b/x-pack/plugins/security/public/management/users/index.ts @@ -4,6 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { saveRole, deleteRole } from './lib/roles'; - -export { getFields } from './lib/get_fields'; +export { UserAPIClient } from './user_api_client'; +export { usersManagementApp } from './users_management_app'; diff --git a/x-pack/plugins/security/public/management/users/user_api_client.mock.ts b/x-pack/plugins/security/public/management/users/user_api_client.mock.ts new file mode 100644 index 00000000000000..7223f78d57fdc0 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/user_api_client.mock.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const userAPIClientMock = { + create: () => ({ + getUsers: jest.fn(), + getUser: jest.fn(), + deleteUser: jest.fn(), + saveUser: jest.fn(), + changePassword: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/management/users/user_api_client.ts b/x-pack/plugins/security/public/management/users/user_api_client.ts new file mode 100644 index 00000000000000..61dd09d2c5e3de --- /dev/null +++ b/x-pack/plugins/security/public/management/users/user_api_client.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpStart } from 'src/core/public'; +import { User, EditUser } from '../../../common/model'; + +const usersUrl = '/internal/security/users'; + +export class UserAPIClient { + constructor(private readonly http: HttpStart) {} + + public async getUsers() { + return await this.http.get(usersUrl); + } + + public async getUser(username: string) { + return await this.http.get(`${usersUrl}/${encodeURIComponent(username)}`); + } + + public async deleteUser(username: string) { + await this.http.delete(`${usersUrl}/${encodeURIComponent(username)}`); + } + + public async saveUser(user: EditUser) { + await this.http.post(`${usersUrl}/${encodeURIComponent(user.username)}`, { + body: JSON.stringify(user), + }); + } + + public async changePassword(username: string, password: string, currentPassword: string) { + const data: Record = { + newPassword: password, + }; + if (currentPassword) { + data.password = currentPassword; + } + + await this.http.post(`${usersUrl}/${encodeURIComponent(username)}/password`, { + body: JSON.stringify(data), + }); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/index.ts b/x-pack/plugins/security/public/management/users/users_grid/index.ts similarity index 82% rename from x-pack/legacy/plugins/security/public/views/management/users_grid/components/index.ts rename to x-pack/plugins/security/public/management/users/users_grid/index.ts index 03721f5ce93b1f..90e16248e19c36 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/index.ts +++ b/x-pack/plugins/security/public/management/users/users_grid/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { UsersListPage } from './users_list_page'; +export { UsersGridPage } from './users_grid_page'; diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.test.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx similarity index 63% rename from x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.test.tsx rename to x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx index bdc0df9bae67c3..def0649953437d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx @@ -4,19 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UserAPIClient } from '../../../../lib/api'; -import { User } from '../../../../../common/model'; +import { User } from '../../../../common/model'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { UsersListPage } from './users_list_page'; +import { UsersGridPage } from './users_grid_page'; import React from 'react'; import { ReactWrapper } from 'enzyme'; +import { userAPIClientMock } from '../index.mock'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; -jest.mock('ui/kfetch'); - -describe('UsersListPage', () => { +describe('UsersGridPage', () => { it('renders the list of users', async () => { - const apiClient = new UserAPIClient(); - apiClient.getUsers = jest.fn().mockImplementation(() => { + const apiClientMock = userAPIClientMock.create(); + apiClientMock.getUsers.mockImplementation(() => { return Promise.resolve([ { username: 'foo', @@ -38,22 +37,27 @@ describe('UsersListPage', () => { ]); }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); await waitForRender(wrapper); - expect(apiClient.getUsers).toBeCalledTimes(1); + expect(apiClientMock.getUsers).toBeCalledTimes(1); expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); expect(wrapper.find('EuiTableRow')).toHaveLength(2); }); it('renders a forbidden message if user is not authorized', async () => { - const apiClient = new UserAPIClient(); - apiClient.getUsers = jest.fn().mockImplementation(() => { - return Promise.reject({ body: { statusCode: 403 } }); - }); + const apiClient = userAPIClientMock.create(); + apiClient.getUsers.mockRejectedValue({ body: { statusCode: 403 } }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); await waitForRender(wrapper); diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx similarity index 85% rename from x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx rename to x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx index df8522e5f32f91..fa15c3388fcc99 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx @@ -19,15 +19,16 @@ import { EuiEmptyPrompt, EuiBasicTableColumn, } from '@elastic/eui'; -import { toastNotifications } from 'ui/notify'; -import { injectI18n, FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; -import { ConfirmDeleteUsers } from '../../../../components/management/users'; -import { User } from '../../../../../common/model'; -import { UserAPIClient } from '../../../../lib/api'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NotificationsStart } from 'src/core/public'; +import { User } from '../../../../common/model'; +import { ConfirmDeleteUsers } from '../components'; +import { UserAPIClient } from '..'; interface Props { - intl: InjectedIntl; - apiClient: UserAPIClient; + apiClient: PublicMethodsOf; + notifications: NotificationsStart; } interface State { @@ -38,7 +39,7 @@ interface State { filter: string; } -class UsersListPageUI extends Component { +export class UsersGridPage extends Component { constructor(props: Props) { super(props); this.state = { @@ -56,7 +57,6 @@ class UsersListPageUI extends Component { public render() { const { users, filter, permissionDenied, showDeleteConfirmation, selection } = this.state; - const { intl } = this.props; if (permissionDenied) { return ( @@ -88,8 +88,7 @@ class UsersListPageUI extends Component { const columns: Array> = [ { field: 'full_name', - name: intl.formatMessage({ - id: 'xpack.security.management.users.fullNameColumnName', + name: i18n.translate('xpack.security.management.users.fullNameColumnName', { defaultMessage: 'Full Name', }), sortable: true, @@ -100,8 +99,7 @@ class UsersListPageUI extends Component { }, { field: 'username', - name: intl.formatMessage({ - id: 'xpack.security.management.users.userNameColumnName', + name: i18n.translate('xpack.security.management.users.userNameColumnName', { defaultMessage: 'User Name', }), sortable: true, @@ -114,8 +112,7 @@ class UsersListPageUI extends Component { }, { field: 'email', - name: intl.formatMessage({ - id: 'xpack.security.management.users.emailAddressColumnName', + name: i18n.translate('xpack.security.management.users.emailAddressColumnName', { defaultMessage: 'Email Address', }), sortable: true, @@ -126,8 +123,7 @@ class UsersListPageUI extends Component { }, { field: 'roles', - name: intl.formatMessage({ - id: 'xpack.security.management.users.rolesColumnName', + name: i18n.translate('xpack.security.management.users.rolesColumnName', { defaultMessage: 'Roles', }), render: (rolenames: string[]) => { @@ -144,15 +140,13 @@ class UsersListPageUI extends Component { }, { field: 'metadata', - name: intl.formatMessage({ - id: 'xpack.security.management.users.reservedColumnName', + name: i18n.translate('xpack.security.management.users.reservedColumnName', { defaultMessage: 'Reserved', }), sortable: ({ metadata }: User) => Boolean(metadata && metadata._reserved), width: '100px', align: 'right', - description: intl.formatMessage({ - id: 'xpack.security.management.users.reservedColumnDescription', + description: i18n.translate('xpack.security.management.users.reservedColumnDescription', { defaultMessage: 'Reserved users are built-in and cannot be removed. Only the password can be changed.', }), @@ -233,6 +227,7 @@ class UsersListPageUI extends Component { usersToDelete={selection.map(user => user.username)} callback={this.handleDelete} apiClient={this.props.apiClient} + notifications={this.props.notifications} /> ) : null} @@ -275,14 +270,11 @@ class UsersListPageUI extends Component { if (e.body.statusCode === 403) { this.setState({ permissionDenied: true }); } else { - toastNotifications.addDanger( - this.props.intl.formatMessage( - { - id: 'xpack.security.management.users.fetchingUsersErrorMessage', - defaultMessage: 'Error fetching users: {message}', - }, - { message: e.body.message } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.security.management.users.fetchingUsersErrorMessage', { + defaultMessage: 'Error fetching users: {message}', + values: { message: e.body.message }, + }) ); } } @@ -315,5 +307,3 @@ class UsersListPageUI extends Component { this.setState({ showDeleteConfirmation: false }); }; } - -export const UsersListPage = injectI18n(UsersListPageUI); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx new file mode 100644 index 00000000000000..48ffcfc550a849 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('./users_grid', () => ({ + UsersGridPage: (props: any) => `Users Page: ${JSON.stringify(props)}`, +})); + +jest.mock('./edit_user', () => ({ + EditUserPage: (props: any) => `User Edit Page: ${JSON.stringify(props)}`, +})); + +import { usersManagementApp } from './users_management_app'; + +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { securityMock } from '../../mocks'; + +async function mountApp(basePath: string) { + const container = document.createElement('div'); + const setBreadcrumbs = jest.fn(); + + const unmount = await usersManagementApp + .create({ + authc: securityMock.createSetup().authc, + getStartServices: coreMock.createSetup().getStartServices as any, + }) + .mount({ basePath, element: container, setBreadcrumbs }); + + return { unmount, container, setBreadcrumbs }; +} + +describe('usersManagementApp', () => { + it('create() returns proper management app descriptor', () => { + expect( + usersManagementApp.create({ + authc: securityMock.createSetup().authc, + getStartServices: coreMock.createSetup().getStartServices as any, + }) + ).toMatchInlineSnapshot(` + Object { + "id": "users", + "mount": [Function], + "order": 10, + "title": "Users", + } + `); + }); + + it('mount() works for the `grid` page', async () => { + const basePath = '/some-base-path/users'; + window.location.hash = basePath; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Users' }]); + expect(container).toMatchInlineSnapshot(` +
+ Users Page: {"notifications":{"toasts":{}},"apiClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `create user` page', async () => { + const basePath = '/some-base-path/users'; + window.location.hash = `${basePath}/edit`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Users' }, + { text: 'Create' }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ User Edit Page: {"authc":{},"apiClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}}} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `edit user` page', async () => { + const basePath = '/some-base-path/users'; + const userName = 'someUserName'; + window.location.hash = `${basePath}/edit/${userName}`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Users' }, + { href: `#/some-base-path/users/edit/${userName}`, text: userName }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ User Edit Page: {"authc":{},"apiClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName"} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() properly encodes user name in `edit user` page link in breadcrumbs', async () => { + const basePath = '/some-base-path/users'; + const username = 'some 安全性 user'; + window.location.hash = `${basePath}/edit/${username}`; + + const { setBreadcrumbs } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Users' }, + { + href: '#/some-base-path/users/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', + text: username, + }, + ]); + }); +}); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx new file mode 100644 index 00000000000000..9aebb396ce9a92 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup } from 'src/core/public'; +import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import { AuthenticationServiceSetup } from '../../authentication'; +import { PluginStartDependencies } from '../../plugin'; +import { RolesAPIClient } from '../roles'; +import { UserAPIClient } from './user_api_client'; +import { UsersGridPage } from './users_grid'; +import { EditUserPage } from './edit_user'; + +interface CreateParams { + authc: AuthenticationServiceSetup; + getStartServices: CoreSetup['getStartServices']; +} + +export const usersManagementApp = Object.freeze({ + id: 'users', + create({ authc, getStartServices }: CreateParams) { + return { + id: this.id, + order: 10, + title: i18n.translate('xpack.security.management.usersTitle', { defaultMessage: 'Users' }), + async mount({ basePath, element, setBreadcrumbs }) { + const [{ http, notifications, i18n: i18nStart }] = await getStartServices(); + const usersBreadcrumbs = [ + { + text: i18n.translate('xpack.security.users.breadcrumb', { defaultMessage: 'Users' }), + href: `#${basePath}`, + }, + ]; + + const userAPIClient = new UserAPIClient(http); + const UsersGridPageWithBreadcrumbs = () => { + setBreadcrumbs(usersBreadcrumbs); + return ; + }; + + const EditUserPageWithBreadcrumbs = () => { + const { username } = useParams<{ username?: string }>(); + + setBreadcrumbs([ + ...usersBreadcrumbs, + username + ? { text: username, href: `#${basePath}/edit/${encodeURIComponent(username)}` } + : { + text: i18n.translate('xpack.security.users.createBreadcrumb', { + defaultMessage: 'Create', + }), + }, + ]); + + return ( + + ); + }; + + render( + + + + + + + + + + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; + }, + } as RegisterManagementAppArgs; + }, +}); diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts deleted file mode 100644 index 50e0b838c750fc..00000000000000 --- a/x-pack/plugins/security/public/plugin.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; -import { LicensingPluginSetup } from '../../licensing/public'; -import { - SessionExpired, - SessionTimeout, - ISessionTimeout, - SessionTimeoutHttpInterceptor, - UnauthorizedResponseHttpInterceptor, -} from './session'; -import { SecurityLicenseService } from '../common/licensing'; -import { SecurityNavControlService } from './nav_control'; -import { AuthenticationService } from './authentication'; - -export interface PluginSetupDependencies { - licensing: LicensingPluginSetup; -} - -export class SecurityPlugin implements Plugin { - private sessionTimeout!: ISessionTimeout; - - private navControlService!: SecurityNavControlService; - - private securityLicenseService!: SecurityLicenseService; - - public setup(core: CoreSetup, { licensing }: PluginSetupDependencies) { - const { http, notifications, injectedMetadata } = core; - const { basePath, anonymousPaths } = http; - anonymousPaths.register('/login'); - anonymousPaths.register('/logout'); - anonymousPaths.register('/logged_out'); - - const tenant = `${injectedMetadata.getInjectedVar('session.tenant', '')}`; - const sessionExpired = new SessionExpired(basePath, tenant); - http.intercept(new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths)); - this.sessionTimeout = new SessionTimeout(notifications, sessionExpired, http, tenant); - http.intercept(new SessionTimeoutHttpInterceptor(this.sessionTimeout, anonymousPaths)); - - this.navControlService = new SecurityNavControlService(); - this.securityLicenseService = new SecurityLicenseService(); - const { license } = this.securityLicenseService.setup({ license$: licensing.license$ }); - - const authc = new AuthenticationService().setup({ http: core.http }); - - this.navControlService.setup({ - securityLicense: license, - authc, - }); - - return { - authc, - sessionTimeout: this.sessionTimeout, - }; - } - - public start(core: CoreStart) { - this.sessionTimeout.start(); - this.navControlService.start({ core }); - } - - public stop() { - this.sessionTimeout.stop(); - this.navControlService.stop(); - this.securityLicenseService.stop(); - } -} - -export type SecurityPluginSetup = ReturnType; -export type SecurityPluginStart = ReturnType; diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx new file mode 100644 index 00000000000000..394e23cbbf646c --- /dev/null +++ b/x-pack/plugins/security/public/plugin.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../src/plugins/home/public'; +import { LicensingPluginSetup } from '../../licensing/public'; +import { ManagementSetup, ManagementStart } from '../../../../src/plugins/management/public'; +import { + SessionExpired, + SessionTimeout, + ISessionTimeout, + SessionTimeoutHttpInterceptor, + UnauthorizedResponseHttpInterceptor, +} from './session'; +import { SecurityLicenseService } from '../common/licensing'; +import { SecurityNavControlService } from './nav_control'; +import { AccountManagementPage } from './account_management'; +import { AuthenticationService, AuthenticationServiceSetup } from './authentication'; +import { ManagementService, UserAPIClient } from './management'; + +export interface PluginSetupDependencies { + licensing: LicensingPluginSetup; + home?: HomePublicPluginSetup; + management?: ManagementSetup; +} + +export interface PluginStartDependencies { + data: DataPublicPluginStart; + management?: ManagementStart; +} + +export class SecurityPlugin + implements + Plugin< + SecurityPluginSetup, + SecurityPluginStart, + PluginSetupDependencies, + PluginStartDependencies + > { + private sessionTimeout!: ISessionTimeout; + private readonly navControlService = new SecurityNavControlService(); + private readonly securityLicenseService = new SecurityLicenseService(); + private readonly managementService = new ManagementService(); + private authc!: AuthenticationServiceSetup; + + public setup( + core: CoreSetup, + { home, licensing, management }: PluginSetupDependencies + ) { + const { http, notifications, injectedMetadata } = core; + const { basePath, anonymousPaths } = http; + anonymousPaths.register('/login'); + anonymousPaths.register('/logout'); + anonymousPaths.register('/logged_out'); + + const tenant = `${injectedMetadata.getInjectedVar('session.tenant', '')}`; + const sessionExpired = new SessionExpired(basePath, tenant); + http.intercept(new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths)); + this.sessionTimeout = new SessionTimeout(notifications, sessionExpired, http, tenant); + http.intercept(new SessionTimeoutHttpInterceptor(this.sessionTimeout, anonymousPaths)); + + const { license } = this.securityLicenseService.setup({ license$: licensing.license$ }); + + this.authc = new AuthenticationService().setup({ http: core.http }); + + this.navControlService.setup({ + securityLicense: license, + authc: this.authc, + }); + + if (management) { + this.managementService.setup({ + license, + management, + authc: this.authc, + fatalErrors: core.fatalErrors, + getStartServices: core.getStartServices, + }); + } + + if (management && home) { + home.featureCatalogue.register({ + id: 'security', + title: i18n.translate('xpack.security.registerFeature.securitySettingsTitle', { + defaultMessage: 'Security Settings', + }), + description: i18n.translate('xpack.security.registerFeature.securitySettingsDescription', { + defaultMessage: + 'Protect your data and easily manage who has access to what with users and roles.', + }), + icon: 'securityApp', + path: '/app/kibana#/management/security/users', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); + } + + return { + authc: this.authc, + sessionTimeout: this.sessionTimeout, + }; + } + + public start(core: CoreStart, { data, management }: PluginStartDependencies) { + this.sessionTimeout.start(); + this.navControlService.start({ core }); + + if (management) { + this.managementService.start({ management }); + } + + return { + __legacyCompat: { + account_management: { + AccountManagementPage: () => ( + + + + ), + }, + }, + }; + } + + public stop() { + this.sessionTimeout.stop(); + this.navControlService.stop(); + this.securityLicenseService.stop(); + this.managementService.stop(); + } +} + +export type SecurityPluginSetup = ReturnType; +export type SecurityPluginStart = ReturnType; diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index 9cd5cf83092e18..def6fabc0e3222 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { RoleMapping } from '../../../../../legacy/plugins/security/common/model'; +import { RoleMapping } from '../../../common/model'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapError } from '../../errors'; import { RouteDefinitionParams } from '..'; diff --git a/x-pack/plugins/spaces/common/index.ts b/x-pack/plugins/spaces/common/index.ts index 65baa1bd991022..c1f0f8bd3ece41 100644 --- a/x-pack/plugins/spaces/common/index.ts +++ b/x-pack/plugins/spaces/common/index.ts @@ -5,5 +5,5 @@ */ export { isReservedSpace } from './is_reserved_space'; -export { MAX_SPACE_INITIALS } from './constants'; +export { MAX_SPACE_INITIALS, SPACE_SEARCH_COUNT_THRESHOLD } from './constants'; export { addSpaceIdToPath, getSpaceIdFromPath } from './lib/spaces_url_parser'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ce3edbbb598280..c3e46a9c04e1cc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10595,17 +10595,6 @@ "xpack.security.management.apiKeys.table.userFilterLabel": "ユーザー", "xpack.security.management.apiKeys.table.userNameColumnName": "ユーザー", "xpack.security.management.apiKeysTitle": "API キー", - "xpack.security.management.changePasswordForm.cancelButtonLabel": "キャンセル", - "xpack.security.management.changePasswordForm.changePasswordLinkLabel": "パスワードを変更", - "xpack.security.management.changePasswordForm.confirmPasswordLabel": "パスワードの確認", - "xpack.security.management.changePasswordForm.currentPasswordLabel": "現在のパスワード", - "xpack.security.management.changePasswordForm.incorrectPasswordDescription": "入力された現在のパスワードが正しくありません。", - "xpack.security.management.changePasswordForm.newPasswordLabel": "新しいパスワード", - "xpack.security.management.changePasswordForm.passwordDontMatchDescription": "パスワードが一致しません", - "xpack.security.management.changePasswordForm.passwordLabel": "パスワード", - "xpack.security.management.changePasswordForm.passwordLengthDescription": "パスワードは最低 6 文字必要です", - "xpack.security.management.changePasswordForm.saveChangesButtonLabel": "変更を保存", - "xpack.security.management.changePasswordForm.updateAndRestartKibanaDescription": "Kibana ユーザーのパスワードを変更後、kibana.yml ファイルを更新し Kibana を再起動する必要があります。", "xpack.security.management.editRole.cancelButtonLabel": "キャンセル", "xpack.security.management.editRole.changeAllPrivilegesLink": "(すべて変更)", "xpack.security.management.editRole.collapsiblePanel.hideLinkText": "非表示", @@ -10730,10 +10719,6 @@ "xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton": "スペース権限を作成", "xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton": "グローバル特権を更新", "xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton": "スペース権限を更新", - "xpack.security.management.passwordForm.confirmPasswordLabel": "パスワードの確認", - "xpack.security.management.passwordForm.passwordDontMatchDescription": "パスワードが一致しません", - "xpack.security.management.passwordForm.passwordLabel": "パスワード", - "xpack.security.management.passwordForm.passwordLengthDescription": "パスワードは最低 6 文字必要です", "xpack.security.management.roles.actionsColumnName": "アクション", "xpack.security.management.roles.cloneRoleActionName": "{roleName} を複製", "xpack.security.management.roles.confirmDelete.cancelButtonLabel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dc72e83ebe3c30..c40b8c8d0393c8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10594,17 +10594,6 @@ "xpack.security.management.apiKeys.table.userFilterLabel": "用户", "xpack.security.management.apiKeys.table.userNameColumnName": "用户", "xpack.security.management.apiKeysTitle": "API 密钥", - "xpack.security.management.changePasswordForm.cancelButtonLabel": "取消", - "xpack.security.management.changePasswordForm.changePasswordLinkLabel": "更改密码", - "xpack.security.management.changePasswordForm.confirmPasswordLabel": "确认密码", - "xpack.security.management.changePasswordForm.currentPasswordLabel": "当前密码", - "xpack.security.management.changePasswordForm.incorrectPasswordDescription": "您输入的当前密码不正确。", - "xpack.security.management.changePasswordForm.newPasswordLabel": "新密码", - "xpack.security.management.changePasswordForm.passwordDontMatchDescription": "密码不匹配", - "xpack.security.management.changePasswordForm.passwordLabel": "密码", - "xpack.security.management.changePasswordForm.passwordLengthDescription": "密码长度必须至少为 6 个字符", - "xpack.security.management.changePasswordForm.saveChangesButtonLabel": "保存更改", - "xpack.security.management.changePasswordForm.updateAndRestartKibanaDescription": "更改 Kibana 用户的密码后,必须更新 kibana.yml 文件并重新启动 Kibana", "xpack.security.management.editRole.cancelButtonLabel": "取消", "xpack.security.management.editRole.changeAllPrivilegesLink": "(全部更改)", "xpack.security.management.editRole.collapsiblePanel.hideLinkText": "隐藏", @@ -10729,10 +10718,6 @@ "xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton": "创建工作区权限", "xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton": "更新全局权限", "xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton": "更新工作区权限", - "xpack.security.management.passwordForm.confirmPasswordLabel": "确认密码", - "xpack.security.management.passwordForm.passwordDontMatchDescription": "密码不匹配", - "xpack.security.management.passwordForm.passwordLabel": "密码", - "xpack.security.management.passwordForm.passwordLengthDescription": "密码长度必须至少为 6 个字符", "xpack.security.management.roles.actionsColumnName": "鎿嶄綔", "xpack.security.management.roles.cloneRoleActionName": "克隆 {roleName}", "xpack.security.management.roles.confirmDelete.cancelButtonLabel": "取消", diff --git a/x-pack/test/functional/apps/security/management.js b/x-pack/test/functional/apps/security/management.js index 45a35029ffba2e..8ab84126b2b30a 100644 --- a/x-pack/test/functional/apps/security/management.js +++ b/x-pack/test/functional/apps/security/management.js @@ -11,7 +11,7 @@ import { ROLES_PATH, EDIT_ROLES_PATH, CLONE_ROLES_PATH, -} from '../../../../legacy/plugins/security/public/views/management/management_urls'; +} from '../../../../plugins/security/public/management/management_urls'; export default function({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); diff --git a/x-pack/test/functional/apps/security/role_mappings.ts b/x-pack/test/functional/apps/security/role_mappings.ts index 5fed56ee79e3dd..a1517e1934a286 100644 --- a/x-pack/test/functional/apps/security/role_mappings.ts +++ b/x-pack/test/functional/apps/security/role_mappings.ts @@ -93,7 +93,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const url = parse(await browser.getCurrentUrl()); - expect(url.hash).to.eql('#/management/security/role_mappings?_g=()'); + expect(url.hash).to.eql('#/management/security/role_mappings'); }); describe('with role mappings', () => { From a131f1dbcfa4838653d587e948ef0365036e5dd6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 21 Jan 2020 13:50:18 +0100 Subject: [PATCH 11/59] [ML] Formatting for additional timing and model size stats (#55062) * [ML] formatting for additional timing and model size stats * [ML] roundToDecimalPlace only average search time * [ML] adjust functional tests * [ML] remove debug tag, fix assert value * [ML] check for no decimal place * [ML] fix functional tests Co-authored-by: Elastic Machine --- .../formatters/round_to_decimal_place.test.ts | 2 ++ .../formatters/round_to_decimal_place.ts | 7 ++++++- .../components/job_details/format_values.js | 10 ++++++++++ .../anomaly_detection/advanced_job.ts | 8 ++++---- .../anomaly_detection/multi_metric_job.ts | 4 ++-- .../anomaly_detection/population_job.ts | 4 ++-- .../anomaly_detection/saved_search_job.ts | 20 +++++++++---------- .../anomaly_detection/single_metric_job.ts | 4 ++-- 8 files changed, 38 insertions(+), 21 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts index 99b9aceab3696f..663a3f3d8f9556 100644 --- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts @@ -20,6 +20,7 @@ describe('ML - roundToDecimalPlace formatter', () => { expect(roundToDecimalPlace(0.0005)).toBe('5.00e-4'); expect(roundToDecimalPlace(-0.0005)).toBe('-5.00e-4'); expect(roundToDecimalPlace(-12.045)).toBe(-12.04); + expect(roundToDecimalPlace(0)).toBe(0); }); it('returns the correct format using specified decimal place', () => { @@ -31,5 +32,6 @@ describe('ML - roundToDecimalPlace formatter', () => { expect(roundToDecimalPlace(0.0005, 4)).toBe(0.0005); expect(roundToDecimalPlace(0.00005, 4)).toBe('5.00e-5'); expect(roundToDecimalPlace(-0.00005, 4)).toBe('-5.00e-5'); + expect(roundToDecimalPlace(0, 4)).toBe(0); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts index f863fe6d76e57d..5a030d7619e986 100644 --- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts +++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export function roundToDecimalPlace(num: number, dp: number = 2) { +export function roundToDecimalPlace(num: number, dp: number = 2): number | string { + if (num % 1 === 0) { + // no decimal place + return num; + } + if (Math.abs(num) < Math.pow(10, -dp)) { return Number.parseFloat(String(num)).toExponential(2); } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js index eb0a905725d750..9984f3be299ae8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js @@ -6,6 +6,7 @@ import numeral from '@elastic/numeral'; import { formatDate } from '@elastic/eui/lib/services/format'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; import { toLocaleString } from '../../../../util/string_utils'; const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; @@ -35,6 +36,8 @@ export function formatValues([key, value]) { case 'established_model_memory': case 'input_bytes': case 'model_bytes': + case 'model_bytes_exceeded': + case 'model_bytes_memory_limit': value = formatData(value); break; @@ -53,9 +56,16 @@ export function formatValues([key, value]) { case 'total_over_field_count': case 'total_partition_field_count': case 'bucket_allocation_failures_count': + case 'search_count': value = toLocaleString(value); break; + // numbers rounded to 3 decimal places + case 'average_search_time_per_bucket_ms': + case 'exponential_average_search_time_per_hour_ms': + value = typeof value === 'number' ? roundToDecimalPlace(value, 3).toLocaleString() : value; + break; + default: break; } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 386914b735554c..acb92b270c4a12 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -194,8 +194,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '10485760', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '10.0 MB', total_by_field_count: '37', total_over_field_count: '92', total_partition_field_count: '8', @@ -261,8 +261,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '104857600', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '100.0 MB', total_by_field_count: '994', total_over_field_count: '0', total_partition_field_count: '2', diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index d41d96e40e2bee..6a12a28e8ac490 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -60,8 +60,8 @@ export default function({ getService }: FtrProviderContext) { return { job_id: expectedJobId, result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '59', total_over_field_count: '0', total_partition_field_count: '58', diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index 296af3179ce3ee..6593dd10928b4b 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -74,8 +74,8 @@ export default function({ getService }: FtrProviderContext) { return { job_id: expectedJobId, result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '8388608', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '8.0 MB', total_by_field_count: '25', total_over_field_count: '92', total_partition_field_count: '3', diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts index 7d989bc6244b81..348910a2a8f84a 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts @@ -53,8 +53,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -104,8 +104,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '7', total_over_field_count: '0', total_partition_field_count: '6', @@ -155,8 +155,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '7', total_over_field_count: '0', total_partition_field_count: '6', @@ -207,8 +207,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -258,8 +258,8 @@ export default function({ getService }: FtrProviderContext) { }, modelSizeStats: { result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts index f6cd7b40bc7b1d..13cac36d99a1ba 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts @@ -59,8 +59,8 @@ export default function({ getService }: FtrProviderContext) { return { job_id: expectedJobId, result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '15728640', + model_bytes_exceeded: '0.0 B', + model_bytes_memory_limit: '15.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', From c1960583500d28fc3002b4959804ad52cf723b4a Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Tue, 21 Jan 2020 14:04:52 +0100 Subject: [PATCH 12/59] [SIEM] Update ml_conditional_links cypress tests (#55373) --- .../ml_conditional_links/ml_conditional_links.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts index afeb8c3c13a4fd..142729189e49b3 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts @@ -99,7 +99,7 @@ describe('ml conditional links', () => { loginAndWaitForPage(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', - '/app/siem#/network/ip/127.0.0.1?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + '/app/siem#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' ); }); @@ -107,7 +107,7 @@ describe('ml conditional links', () => { loginAndWaitForPage(mlNetworkSingleIpKqlQuery); cy.url().should( 'include', - "/app/siem#/network/ip/127.0.0.1?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + "/app/siem#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" ); }); From c88aa5a505ced73ffabd4a4136f6172ee23073c8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 21 Jan 2020 14:49:30 +0100 Subject: [PATCH 13/59] Migration: Separate legacy and index entrypoint (#54124) --- src/legacy/core_plugins/kibana/index.js | 7 +- .../core_plugins/kibana/public/.eslintrc.js | 1 + .../kibana/public/dashboard/index.ts | 40 ++---------- .../kibana/public/dashboard/legacy.ts | 56 ++++++++++++++++ .../__tests__/directives/discover_field.js | 2 +- .../__tests__/directives/field_calculator.js | 2 +- .../__tests__/directives/field_chooser.js | 2 +- .../discover/__tests__/doc_table/doc_table.js | 2 +- .../__tests__/doc_table/lib/rows_headers.js | 2 +- .../query_parameters/action_add_filter.js | 2 +- .../action_set_predecessor_count.js | 2 +- .../action_set_query_parameters.js | 2 +- .../action_set_successor_count.js | 2 +- .../kibana/public/discover/index.ts | 17 +---- .../kibana/public/discover/legacy.ts | 32 +++++++++ .../angular/context/api/__tests__/anchor.js | 2 +- .../context/api/__tests__/predecessors.js | 2 +- .../context/api/__tests__/successors.js | 2 +- .../core_plugins/kibana/public/home/index.ts | 5 -- .../kibana/public/home/kibana_services.ts | 1 - .../home/np_ready/components/welcome.test.tsx | 4 -- .../home/np_ready/components/welcome.tsx | 7 +- .../core_plugins/kibana/public/home/plugin.ts | 8 ++- .../core_plugins/kibana/public/kibana.js | 6 +- .../kibana/public/visualize/index.ts | 49 ++------------ .../kibana/public/visualize/legacy.ts | 65 +++++++++++++++++++ .../directives/__tests__/css_truncate.js | 2 +- .../dashboard_mode/public/dashboard_viewer.js | 1 + 28 files changed, 202 insertions(+), 123 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/legacy.ts create mode 100644 src/legacy/core_plugins/kibana/public/discover/legacy.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/legacy.ts diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index e6a0420534ef25..0366d8b27f2118 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -60,7 +60,12 @@ export default function(kibana) { }, uiExports: { - hacks: ['plugins/kibana/discover', 'plugins/kibana/dev_tools', 'plugins/kibana/visualize'], + hacks: [ + 'plugins/kibana/discover/legacy', + 'plugins/kibana/dev_tools', + 'plugins/kibana/visualize/legacy', + 'plugins/kibana/dashboard/legacy', + ], savedObjectTypes: ['plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register'], app: { id: 'kibana', diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js index 160adcc5b63f1b..9b45217287dc8c 100644 --- a/src/legacy/core_plugins/kibana/public/.eslintrc.js +++ b/src/legacy/core_plugins/kibana/public/.eslintrc.js @@ -51,6 +51,7 @@ function buildRestrictedPaths(shimmedPlugins) { from: [ `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`, + `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/legacy.ts`, ], allowSameFolder: false, errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index fd39f28a7673a1..4a8decab6b00e0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -17,40 +17,12 @@ * under the License. */ -import { npSetup, npStart, legacyChrome } from './legacy_imports'; -import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as data } from '../../../data/public/legacy'; -import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import './saved_dashboard/saved_dashboard_register'; -import './dashboard_config'; +import { PluginInitializerContext } from 'kibana/public'; +import { DashboardPlugin } from './plugin'; export * from './np_ready/dashboard_constants'; -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await legacyChrome.dangerouslyGetActiveInjector(); - - return { - dashboardConfig: injector.get('dashboardConfig'), - }; -} - -(async () => { - const instance = new DashboardPlugin(); - instance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - getAngularDependencies, - }, - }); - instance.start(npStart.core, { - ...npStart.plugins, - data, - npData: npStart.plugins.data, - embeddables, - navigation: npStart.plugins.navigation, - }); -})(); +// Core will be looking for this when loading our plugin in the new platform +export const plugin = (context: PluginInitializerContext) => { + return new DashboardPlugin(); +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts new file mode 100644 index 00000000000000..068a8378f936a3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart, legacyChrome } from './legacy_imports'; +import { LegacyAngularInjectedDependencies } from './plugin'; +import { start as data } from '../../../data/public/legacy'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import './saved_dashboard/saved_dashboard_register'; +import './dashboard_config'; +import { plugin } from './index'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + return { + dashboardConfig: injector.get('dashboardConfig'), + }; +} + +(async () => { + const instance = plugin({} as PluginInitializerContext); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + data, + npData: npStart.plugins.data, + embeddables, + navigation: npStart.plugins.navigation, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 18d83595f8fa30..6ffda87ac2be8d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index 378a9e93256557..f302d684135f6c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import ngMock from 'ng_mock'; import { fieldCalculator } from '../../np_ready/components/field_chooser/lib/field_calculator'; import expect from '@kbn/expect'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index 5f6898ae2bd164..f74e145865475d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { SimpleSavedObject } from '../../../../../../../core/public'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js index b57f452b637afa..6b97da79fc5893 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import hits from 'fixtures/real_hits'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js index 012f2b6061ee42..c19e033ccb72df 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js @@ -24,7 +24,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { getFakeRow, getFakeRowVals } from 'fixtures/fake_row'; import $ from 'jquery'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; describe('Doc Table', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js index 90614cf3c132c2..f2acbf363d825d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js @@ -22,7 +22,7 @@ import ngMock from 'ng_mock'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; import { createIndexPatternsStub } from '../../np_ready/angular/context/api/__tests__/_stubs'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { npStart } from 'ui/new_platform'; describe('context app', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js index 1ad4bdbea210db..9ba425bb0e489e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js index e9ec2c300faa11..39dde2d8bb7cf0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js index 15f3eefac3fd1a..c05f5b4aff3bc7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 7bde30e0d0f0ef..d851cb96a18c48 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -16,24 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import chrome from 'ui/chrome'; + import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; import { DiscoverPlugin } from './plugin'; +export { createSavedSearchesService } from './saved_searches/saved_searches'; + // Core will be looking for this when loading our plugin in the new platform export const plugin = (context: PluginInitializerContext) => { return new DiscoverPlugin(); }; - -// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file -export const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - chrome, - }, -}); -export const start = pluginInstance.start(npStart.core, npStart.plugins); - -export { createSavedSearchesService } from './saved_searches/saved_searches'; diff --git a/src/legacy/core_plugins/kibana/public/discover/legacy.ts b/src/legacy/core_plugins/kibana/public/discover/legacy.ts new file mode 100644 index 00000000000000..2ec64177156f9e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/legacy.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import chrome from 'ui/chrome'; +import { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; +import { plugin } from './index'; + +// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file +export const pluginInstance = plugin({} as PluginInitializerContext); +export const setup = pluginInstance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + chrome, + }, +}); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js index 45ce6cc9d0af2e..debcccebbd11c0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js index 266a505f6be14b..c24b6ac6307ffd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js index e06d414ba260ce..d4c00930c93839 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index b2d90f1444654a..27d09a53ba20dc 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -22,11 +22,8 @@ import { npSetup, npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; -export const trackUiMetric = createUiStatsReporter('Kibana_home'); - /** * Get dependencies relying on the global angular context. * They also have to get resolved together with the legacy imports above @@ -54,9 +51,7 @@ let copiedLegacyCatalogue = false; instance.setup(npSetup.core, { ...npSetup.plugins, __LEGACY: { - trackUiMetric, metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - METRIC_TYPE, getFeatureCatalogueEntries: async () => { if (!copiedLegacyCatalogue) { const injector = await chrome.dangerouslyGetActiveInjector(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 0eb55a3902edac..4d9177735556df 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -55,7 +55,6 @@ export interface HomeKibanaServices { savedObjectsClient: SavedObjectsClientContract; toastNotifications: NotificationsSetup['toasts']; banners: OverlayStart['banners']; - METRIC_TYPE: any; trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; getBasePath: () => string; shouldShowTelemetryOptIn: boolean; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx index 28bdab14193c40..55c469fa58fc61 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx @@ -25,10 +25,6 @@ jest.mock('../../kibana_services', () => ({ getServices: () => ({ addBasePath: (path: string) => `root${path}`, trackUiMetric: () => {}, - METRIC_TYPE: { - LOADED: 'loaded', - CLICK: 'click', - }, }), })); diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx index 9bbb7aaceb915b..1b7761d068d2f2 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx @@ -35,6 +35,7 @@ import { EuiIcon, EuiPortal, } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { FormattedMessage } from '@kbn/i18n/react'; import { getServices } from '../../kibana_services'; @@ -64,17 +65,17 @@ export class Welcome extends React.Component { } private onSampleDataDecline = () => { - this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); this.props.onSkip(); }; private onSampleDataConfirm = () => { - this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); this.redirecToSampleData(); }; componentDidMount() { - this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount'); + this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); this.props.onOptInSeen(); document.addEventListener('keydown', this.hideOnEsc); } diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 42ab049eb5b3a8..502c8f45646cf0 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -18,11 +18,11 @@ */ import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; -import { UiStatsMetricType } from '@kbn/analytics'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { Environment, FeatureCatalogueEntry, @@ -41,8 +41,6 @@ export interface HomePluginStartDependencies { export interface HomePluginSetupDependencies { __LEGACY: { - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - METRIC_TYPE: any; metadata: { app: unknown; bundleId: string; @@ -59,6 +57,7 @@ export interface HomePluginSetupDependencies { getFeatureCatalogueEntries: () => Promise; getAngularDependencies: () => Promise; }; + usageCollection: UsageCollectionSetup; kibana_legacy: KibanaLegacySetup; } @@ -71,6 +70,7 @@ export class HomePlugin implements Plugin { core: CoreSetup, { kibana_legacy, + usageCollection, __LEGACY: { getAngularDependencies, ...legacyServices }, }: HomePluginSetupDependencies ) { @@ -78,9 +78,11 @@ export class HomePlugin implements Plugin { id: 'home', title: 'Home', mount: async ({ core: contextCore }, params) => { + const trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, 'Kibana_home'); const angularDependencies = await getAngularDependencies(); setServices({ ...legacyServices, + trackUiMetric, http: contextCore.http, toastNotifications: core.notifications.toasts, banners: contextCore.overlays.banners, diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 4100ae72058699..bd947b9cb9d7f1 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -47,9 +47,9 @@ import 'uiExports/interpreter'; import 'ui/autoload/all'; import 'ui/kbn_top_nav'; import './home'; -import './discover'; -import './visualize'; -import './dashboard'; +import './discover/legacy'; +import './visualize/legacy'; +import './dashboard/legacy'; import './management'; import './dev_tools'; import 'ui/color_maps'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index f113c81256f8e4..a39779792b83a7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,50 +17,15 @@ * under the License. */ -import { - IPrivate, - legacyChrome, - npSetup, - npStart, - VisEditorTypesRegistryProvider, -} from './legacy_imports'; -import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { PluginInitializerContext } from 'kibana/public'; +import { VisualizePlugin } from './plugin'; export * from './np_ready/visualize_constants'; export { showNewVisModal } from './np_ready/wizard'; -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await legacyChrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - const editorTypes = Private(VisEditorTypesRegistryProvider); - - return { - legacyChrome, - editorTypes, - }; -} - -(() => { - const instance = new VisualizePlugin(); - instance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - getAngularDependencies, - }, - }); - instance.start(npStart.core, { - ...npStart.plugins, - embeddables, - visualizations, - }); -})(); - export { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; + +// Core will be looking for this when loading our plugin in the new platform +export const plugin = (context: PluginInitializerContext) => { + return new VisualizePlugin(); +}; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts new file mode 100644 index 00000000000000..2a1b6130fac899 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'ui/collapsible_sidebar'; // used in default editor + +import { PluginInitializerContext } from 'kibana/public'; +import { + IPrivate, + legacyChrome, + npSetup, + npStart, + VisEditorTypesRegistryProvider, +} from './legacy_imports'; +import { LegacyAngularInjectedDependencies } from './plugin'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { plugin } from './index'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const editorTypes = Private(VisEditorTypesRegistryProvider); + + return { + legacyChrome, + editorTypes, + }; +} + +(() => { + const instance = plugin({} as PluginInitializerContext); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + embeddables, + visualizations, + }); +})(); diff --git a/src/legacy/ui/public/directives/__tests__/css_truncate.js b/src/legacy/ui/public/directives/__tests__/css_truncate.js index 9d470c10358ccf..bf102f5a29fdb9 100644 --- a/src/legacy/ui/public/directives/__tests__/css_truncate.js +++ b/src/legacy/ui/public/directives/__tests__/css_truncate.js @@ -20,7 +20,7 @@ import angular from 'angular'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import 'plugins/kibana/discover/index'; +import 'plugins/kibana/discover/legacy'; let $parentScope; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 391973f6d909b4..fbf917054edbff 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -34,6 +34,7 @@ import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; +import 'plugins/kibana/dashboard/legacy'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; From 6b02ed804fa858447f1e69627ccf6c121358dcab Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 21 Jan 2020 14:52:32 +0100 Subject: [PATCH 14/59] Migrate session storage short url handling (#55021) --- .../state_session_storage_redirect/index.js | 32 ------- .../package.json | 5 -- src/legacy/server/kbn_server.js | 4 - src/legacy/server/url_shortening/index.js | 20 ----- .../routes/lib/short_url_assert_valid.js | 41 --------- .../routes/lib/short_url_assert_valid.test.js | 63 -------------- .../routes/lib/short_url_error.js | 26 ------ .../routes/lib/short_url_error.test.js | 67 --------------- .../routes/lib/short_url_lookup.js | 43 ---------- .../routes/lib/short_url_lookup.test.js | 84 ------------------- .../url_shortening/url_shortening_mixin.js | 23 ----- src/plugins/newsfeed/public/plugin.tsx | 12 ++- .../share/common/short_url_routes.ts} | 16 ++-- src/plugins/share/public/lib/url_shortener.ts | 5 +- src/plugins/share/public/plugin.test.ts | 17 +++- src/plugins/share/public/plugin.ts | 6 +- .../services/short_url_redirect_app.test.ts | 46 ++++++++++ .../services/short_url_redirect_app.ts} | 39 +++++---- .../share/server/routes/create_routes.ts | 2 + .../share/server/routes/get.ts} | 47 +++++++---- src/plugins/share/server/routes/goto.ts | 10 ++- .../share/server/routes/shorten_url.ts | 3 +- 22 files changed, 151 insertions(+), 460 deletions(-) delete mode 100644 src/legacy/core_plugins/state_session_storage_redirect/index.js delete mode 100644 src/legacy/core_plugins/state_session_storage_redirect/package.json delete mode 100644 src/legacy/server/url_shortening/index.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_error.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_error.test.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_lookup.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js delete mode 100644 src/legacy/server/url_shortening/url_shortening_mixin.js rename src/{legacy/server/url_shortening/routes/create_routes.js => plugins/share/common/short_url_routes.ts} (67%) create mode 100644 src/plugins/share/public/services/short_url_redirect_app.test.ts rename src/{legacy/core_plugins/state_session_storage_redirect/public/index.js => plugins/share/public/services/short_url_redirect_app.ts} (50%) rename src/{legacy/server/url_shortening/routes/goto.js => plugins/share/server/routes/get.ts} (55%) diff --git a/src/legacy/core_plugins/state_session_storage_redirect/index.js b/src/legacy/core_plugins/state_session_storage_redirect/index.js deleted file mode 100644 index 2d4d7c97232c04..00000000000000 --- a/src/legacy/core_plugins/state_session_storage_redirect/index.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - app: { - require: ['kibana'], - title: 'Redirecting', - id: 'stateSessionStorageRedirect', - main: 'plugins/state_session_storage_redirect', - hidden: true, - }, - }, - }); -} diff --git a/src/legacy/core_plugins/state_session_storage_redirect/package.json b/src/legacy/core_plugins/state_session_storage_redirect/package.json deleted file mode 100644 index 21956e5d76d5b1..00000000000000 --- a/src/legacy/core_plugins/state_session_storage_redirect/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "state_session_storage_redirect", - "version": "kibana", - "description": "When using the state:storeInSessionStorage setting with the short-urls, we need some way to get the full URL's hashed states into sessionStorage, this app will grab the URL from the injected state and and put the URL hashed states into sessionStorage before redirecting the user." -} diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 8ab55d4c517b59..6991527a9503c6 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -36,7 +36,6 @@ import * as Plugins from './plugins'; import { indexPatternsMixin } from './index_patterns'; import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; import { capabilitiesMixin } from './capabilities'; -import { urlShorteningMixin } from './url_shortening'; import { serverExtensionsMixin } from './server_extensions'; import { uiMixin } from '../ui'; import { sassMixin } from './sass'; @@ -123,9 +122,6 @@ export default class KbnServer { // setup capabilities routes capabilitiesMixin, - // setup routes for short urls - urlShorteningMixin, - // ensure that all bundles are built, or that the // watch bundle server is running optimizeMixin, diff --git a/src/legacy/server/url_shortening/index.js b/src/legacy/server/url_shortening/index.js deleted file mode 100644 index 031af0618d7bcb..00000000000000 --- a/src/legacy/server/url_shortening/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { urlShorteningMixin } from './url_shortening_mixin'; diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js deleted file mode 100644 index ff2b0f214e5eed..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parse } from 'url'; -import { trim } from 'lodash'; -import Boom from 'boom'; - -export function shortUrlAssertValid(url) { - const { protocol, hostname, pathname } = parse(url); - - if (protocol) { - throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); - } - - if (hostname) { - throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); - } - - const pathnameParts = trim(pathname, '/').split('/'); - if (pathnameParts.length !== 2) { - throw Boom.notAcceptable( - `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` - ); - } -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js deleted file mode 100644 index f83073e6aefe90..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { shortUrlAssertValid } from './short_url_assert_valid'; - -describe('shortUrlAssertValid()', () => { - const invalid = [ - ['protocol', 'http://localhost:5601/app/kibana'], - ['protocol', 'https://localhost:5601/app/kibana'], - ['protocol', 'mailto:foo@bar.net'], - ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url - ['hostname', 'localhost/app/kibana'], - ['hostname and port', 'local.host:5601/app/kibana'], - ['hostname and auth', 'user:pass@localhost.net/app/kibana'], - ['path traversal', '/app/../../not-kibana'], - ['deep path', '/app/kibana/foo'], - ['deep path', '/app/kibana/foo/bar'], - ['base path', '/base/app/kibana'], - ]; - - invalid.forEach(([desc, url]) => { - it(`fails when url has ${desc}`, () => { - try { - shortUrlAssertValid(url); - throw new Error(`expected assertion to throw`); - } catch (err) { - if (!err || !err.isBoom) { - throw err; - } - } - }); - }); - - const valid = [ - '/app/kibana', - '/app/monitoring#angular/route', - '/app/text#document-id', - '/app/some?with=query', - '/app/some?with=query#and-a-hash', - ]; - - valid.forEach(url => { - it(`allows ${url}`, () => { - shortUrlAssertValid(url); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.js deleted file mode 100644 index ed44ba21aa4c41..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Boom from 'boom'; - -export function handleShortUrlError(error) { - return Boom.boomify(error, { - statusCode: error.statusCode || error.status || 500, - }); -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js deleted file mode 100644 index 4eca6320ec834e..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { handleShortUrlError } from './short_url_error'; - -function createErrorWithStatus(status) { - const error = new Error(); - error.status = status; - return error; -} - -function createErrorWithStatusCode(statusCode) { - const error = new Error(); - error.statusCode = statusCode; - return error; -} - -describe('handleShortUrlError()', () => { - const caughtErrorsWithStatus = [ - createErrorWithStatus(401), - createErrorWithStatus(403), - createErrorWithStatus(404), - ]; - - const caughtErrorsWithStatusCode = [ - createErrorWithStatusCode(401), - createErrorWithStatusCode(403), - createErrorWithStatusCode(404), - ]; - - const uncaughtErrors = [new Error(), createErrorWithStatus(500), createErrorWithStatusCode(500)]; - - caughtErrorsWithStatus.forEach(err => { - it(`should handle errors with status of ${err.status}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.status); - }); - }); - - caughtErrorsWithStatusCode.forEach(err => { - it(`should handle errors with statusCode of ${err.statusCode}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.statusCode); - }); - }); - - uncaughtErrors.forEach(err => { - it(`should not handle unknown errors`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(500); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js deleted file mode 100644 index a8a01d1433a7a3..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get } from 'lodash'; - -export function shortUrlLookupProvider(server) { - async function updateMetadata(doc, req) { - try { - await req.getSavedObjectsClient().update('url', doc.id, { - accessDate: new Date(), - accessCount: get(doc, 'attributes.accessCount', 0) + 1, - }); - } catch (err) { - server.log('Warning: Error updating url metadata', err); - //swallow errors. It isn't critical if there is no update. - } - } - - return { - async getUrl(id, req) { - const doc = await req.getSavedObjectsClient().get('url', id); - updateMetadata(doc, req); - - return doc.attributes.url; - }, - }; -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js deleted file mode 100644 index e8bf72a142d117..00000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { shortUrlLookupProvider } from './short_url_lookup'; -import { SavedObjectsClient } from '../../../../../core/server'; - -describe('shortUrlLookupProvider', () => { - const ID = 'bf00ad16941fc51420f91a93428b27a0'; - const TYPE = 'url'; - const URL = 'http://elastic.co'; - const server = { log: sinon.stub() }; - const sandbox = sinon.createSandbox(); - - let savedObjectsClient; - let req; - let shortUrl; - - beforeEach(() => { - savedObjectsClient = { - get: sandbox.stub(), - create: sandbox.stub().returns(Promise.resolve({ id: ID })), - update: sandbox.stub(), - errors: SavedObjectsClient.errors, - }; - - req = { getSavedObjectsClient: () => savedObjectsClient }; - shortUrl = shortUrlLookupProvider(server); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('getUrl', () => { - beforeEach(() => { - const attributes = { accessCount: 2, url: URL }; - savedObjectsClient.get.returns({ id: ID, attributes }); - }); - - it('provides the ID to savedObjectsClient', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.get); - const [type, id] = savedObjectsClient.get.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - }); - - it('returns the url', async () => { - const response = await shortUrl.getUrl(ID, req); - expect(response).toEqual(URL); - }); - - it('increments accessCount', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.update); - const [type, id, attributes] = savedObjectsClient.update.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); - expect(attributes.accessCount).toEqual(3); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/url_shortening_mixin.js b/src/legacy/server/url_shortening/url_shortening_mixin.js deleted file mode 100644 index 867898cac845a3..00000000000000 --- a/src/legacy/server/url_shortening/url_shortening_mixin.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { createRoutes } from './routes/create_routes'; - -export function urlShorteningMixin(kbnServer, server) { - createRoutes(server); -} diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx index 5ea5e5b324717d..c4e042fe452f9c 100644 --- a/src/plugins/newsfeed/public/plugin.tsx +++ b/src/plugins/newsfeed/public/plugin.tsx @@ -23,7 +23,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { NewsfeedPluginInjectedConfig } from '../types'; +import { FetchResult, NewsfeedPluginInjectedConfig } from '../types'; import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button'; import { getApi } from './lib/api'; @@ -54,10 +54,14 @@ export class NewsfeedPublicPlugin implements Plugin { private fetchNewsfeed(core: CoreStart) { const { http, injectedMetadata } = core; - const config = injectedMetadata.getInjectedVar( - 'newsfeed' - ) as NewsfeedPluginInjectedConfig['newsfeed']; + const config = injectedMetadata.getInjectedVar('newsfeed') as + | NewsfeedPluginInjectedConfig['newsfeed'] + | undefined; + if (!config) { + // running in new platform, injected metadata not available + return new Rx.Observable(); + } return getApi(http, config, this.kibanaVersion).pipe( takeUntil(this.stop$), // stop the interval when stop method is called catchError(() => Rx.of(null)) // do not throw error diff --git a/src/legacy/server/url_shortening/routes/create_routes.js b/src/plugins/share/common/short_url_routes.ts similarity index 67% rename from src/legacy/server/url_shortening/routes/create_routes.js rename to src/plugins/share/common/short_url_routes.ts index 9540e7441a268b..7b42534de2ab15 100644 --- a/src/legacy/server/url_shortening/routes/create_routes.js +++ b/src/plugins/share/common/short_url_routes.ts @@ -17,11 +17,15 @@ * under the License. */ -import { shortUrlLookupProvider } from './lib/short_url_lookup'; -import { createGotoRoute } from './goto'; +export const GOTO_PREFIX = '/goto'; -export function createRoutes(server) { - const shortUrlLookup = shortUrlLookupProvider(server); +export const getUrlIdFromGotoRoute = (path: string) => + path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1]; - server.route(createGotoRoute({ server, shortUrlLookup })); -} +export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`; + +export const GETTER_PREFIX = '/api/short_url'; + +export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`; + +export const CREATE_PATH = '/api/shorten_url'; diff --git a/src/plugins/share/public/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts index 29d91bdb1aae6a..f2259f1fabf7d8 100644 --- a/src/plugins/share/public/lib/url_shortener.ts +++ b/src/plugins/share/public/lib/url_shortener.ts @@ -19,6 +19,7 @@ import url from 'url'; import { HttpStart } from 'kibana/public'; +import { CREATE_PATH, getGotoPath } from '../../common/short_url_routes'; export async function shortenUrl( absoluteUrl: string, @@ -34,10 +35,10 @@ export async function shortenUrl( const body = JSON.stringify({ url: relativeUrl }); - const resp = await post('/api/shorten_url', { body }); + const resp = await post(CREATE_PATH, { body }); return url.format({ protocol: parsedUrl.protocol, host: parsedUrl.host, - pathname: `${basePath}/goto/${resp.urlId}`, + pathname: `${basePath}${getGotoPath(resp.urlId)}`, }); } diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts index 5610490be33b33..730814fe9ed237 100644 --- a/src/plugins/share/public/plugin.test.ts +++ b/src/plugins/share/public/plugin.test.ts @@ -20,6 +20,7 @@ import { registryMock, managerMock } from './plugin.test.mocks'; import { SharePlugin } from './plugin'; import { CoreStart } from 'kibana/public'; +import { coreMock } from '../../../core/public/mocks'; describe('SharePlugin', () => { beforeEach(() => { @@ -30,16 +31,28 @@ describe('SharePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { - const setup = await new SharePlugin().setup(); + const coreSetup = coreMock.createSetup(); + const setup = await new SharePlugin().setup(coreSetup); expect(registryMock.setup).toHaveBeenCalledWith(); expect(setup.register).toBeDefined(); }); + + test('registers redirect app', async () => { + const coreSetup = coreMock.createSetup(); + await new SharePlugin().setup(coreSetup); + expect(coreSetup.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'short_url_redirect', + }) + ); + }); }); describe('start', () => { test('wires up and returns show function, but not registry', async () => { + const coreSetup = coreMock.createSetup(); const service = new SharePlugin(); - await service.setup(); + await service.setup(coreSetup); const start = await service.start({} as CoreStart); expect(registryMock.start).toHaveBeenCalled(); expect(managerMock.start).toHaveBeenCalledWith( diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 6d78211cf99544..01c248624950ab 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -17,15 +17,17 @@ * under the License. */ -import { CoreStart, Plugin } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; +import { createShortUrlRedirectApp } from './services/short_url_redirect_app'; export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); - public async setup() { + public async setup(core: CoreSetup) { + core.application.register(createShortUrlRedirectApp(core, window.location)); return { ...this.shareMenuRegistry.setup(), }; diff --git a/src/plugins/share/public/services/short_url_redirect_app.test.ts b/src/plugins/share/public/services/short_url_redirect_app.test.ts new file mode 100644 index 00000000000000..206e637451ec07 --- /dev/null +++ b/src/plugins/share/public/services/short_url_redirect_app.test.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createShortUrlRedirectApp } from './short_url_redirect_app'; +import { coreMock } from '../../../../core/public/mocks'; +import { hashUrl } from '../../../kibana_utils/public'; + +jest.mock('../../../kibana_utils/public', () => ({ hashUrl: jest.fn(x => `${x}/hashed`) })); + +describe('short_url_redirect_app', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch url and redirect to hashed version', async () => { + const coreSetup = coreMock.createSetup({ basePath: 'base' }); + coreSetup.http.get.mockResolvedValueOnce({ url: '/app/abc' }); + const locationMock = { pathname: '/base/goto/12345', href: '' } as Location; + + const { mount } = createShortUrlRedirectApp(coreSetup, locationMock); + await mount(); + + // check for fetching the complete URL + expect(coreSetup.http.get).toHaveBeenCalledWith('/api/short_url/12345'); + // check for hashing the URL returned from the server + expect(hashUrl).toHaveBeenCalledWith('/app/abc'); + // check for redirecting to the prepended path + expect(locationMock.href).toEqual('base/app/abc/hashed'); + }); +}); diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/plugins/share/public/services/short_url_redirect_app.ts similarity index 50% rename from src/legacy/core_plugins/state_session_storage_redirect/public/index.js rename to src/plugins/share/public/services/short_url_redirect_app.ts index 701a5736c7d3b8..6f72b711f66020 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/plugins/share/public/services/short_url_redirect_app.ts @@ -17,24 +17,29 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { hashUrl } from '../../../../plugins/kibana_utils/public'; -import uiRoutes from 'ui/routes'; -import { fatalError } from 'ui/notify'; +import { CoreSetup } from 'kibana/public'; +import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes'; +import { hashUrl } from '../../../kibana_utils/public'; -uiRoutes.enable(); -uiRoutes.when('/', { - resolve: { - url: function(AppState, globalState, $window) { - const redirectUrl = chrome.getInjected('redirectUrl'); - try { - const hashedUrl = hashUrl(redirectUrl); - const url = chrome.addBasePath(hashedUrl); +export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({ + id: 'short_url_redirect', + appRoute: GOTO_PREFIX, + chromeless: true, + title: 'Short URL Redirect', + async mount() { + const urlId = getUrlIdFromGotoRoute(location.pathname); - $window.location = url; - } catch (e) { - fatalError(e); - } - }, + if (!urlId) { + throw new Error('Url id not present in path'); + } + + const response = await core.http.get<{ url: string }>(getUrlPath(urlId)); + const redirectUrl = response.url; + const hashedUrl = hashUrl(redirectUrl); + const url = core.http.basePath.prepend(hashedUrl); + + location.href = url; + + return () => {}; }, }); diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts index bd4b6fdb08791d..22d10c25197c90 100644 --- a/src/plugins/share/server/routes/create_routes.ts +++ b/src/plugins/share/server/routes/create_routes.ts @@ -22,11 +22,13 @@ import { CoreSetup, Logger } from 'kibana/server'; import { shortUrlLookupProvider } from './lib/short_url_lookup'; import { createGotoRoute } from './goto'; import { createShortenUrlRoute } from './shorten_url'; +import { createGetterRoute } from './get'; export function createRoutes({ http }: CoreSetup, logger: Logger) { const shortUrlLookup = shortUrlLookupProvider({ logger }); const router = http.createRouter(); createGotoRoute({ router, shortUrlLookup, http }); + createGetterRoute({ router, shortUrlLookup, http }); createShortenUrlRoute({ router, shortUrlLookup }); } diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/plugins/share/server/routes/get.ts similarity index 55% rename from src/legacy/server/url_shortening/routes/goto.js rename to src/plugins/share/server/routes/get.ts index 7a874786423d81..d6b191341dbe14 100644 --- a/src/legacy/server/url_shortening/routes/goto.js +++ b/src/plugins/share/server/routes/get.ts @@ -17,23 +17,40 @@ * under the License. */ -import { handleShortUrlError } from './lib/short_url_error'; +import { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getUrlPath } from '../../common/short_url_routes'; -export const createGotoRoute = ({ server, shortUrlLookup }) => ({ - method: 'GET', - path: '/goto_LP/{urlId}', - handler: async function(request, h) { - try { - const url = await shortUrlLookup.getUrl(request.params.urlId, request); +export const createGetterRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: getUrlPath('{urlId}'), + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); shortUrlAssertValid(url); - const app = server.getHiddenUiAppById('stateSessionStorageRedirect'); - return h.renderApp(app, { - redirectUrl: url, + return response.ok({ + body: { + url, + }, }); - } catch (err) { - throw handleShortUrlError(err); - } - }, -}); + }) + ); +}; diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts index 7343dc1bd34a26..5c3a4e441099fb 100644 --- a/src/plugins/share/server/routes/goto.ts +++ b/src/plugins/share/server/routes/goto.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getGotoPath } from '../../common/short_url_routes'; export const createGotoRoute = ({ router, @@ -34,7 +35,7 @@ export const createGotoRoute = ({ }) => { router.get( { - path: '/goto/{urlId}', + path: getGotoPath('{urlId}'), validate: { params: schema.object({ urlId: schema.string() }), }, @@ -54,10 +55,13 @@ export const createGotoRoute = ({ }, }); } - return response.redirected({ + const body = await context.core.rendering.render(); + + return response.ok({ headers: { - location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + 'content-security-policy': http.csp.header, }, + body, }); }) ); diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts index 116b90c6971c5c..41570f8a5f453a 100644 --- a/src/plugins/share/server/routes/shorten_url.ts +++ b/src/plugins/share/server/routes/shorten_url.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { CREATE_PATH } from '../../common/short_url_routes'; export const createShortenUrlRoute = ({ shortUrlLookup, @@ -32,7 +33,7 @@ export const createShortenUrlRoute = ({ }) => { router.post( { - path: '/api/shorten_url', + path: CREATE_PATH, validate: { body: schema.object({ url: schema.string() }), }, From d3cef4791ec88da7d364596051e6f86310a800dd Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 21 Jan 2020 08:54:42 -0500 Subject: [PATCH 15/59] [Maps] fix warning about missing key in react element (#55372) --- .../legacy/plugins/maps/public/layers/styles/color_utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js index df212f23cd8942..b04f8ff56e5ee4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js @@ -113,7 +113,11 @@ export const COLOR_PALETTES = COLOR_PALETTES_CONFIGS.map(palette => { height: '100%', display: 'inline-block', }; - return
 
; + return ( +
+   +
+ ); }); return { value: palette.id, From 6feabcd53342482d403247487dfc7bb6ca6e74d9 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Tue, 21 Jan 2020 17:38:28 +0300 Subject: [PATCH 16/59] Generate a static parser, move tests to vis_type_timelion (#55299) * Use generated parser, move tests to vis_type_timelion * Remove legacy tests * Create a grunt task for generating a parser --- .eslintignore | 1 + .../core_plugins/vis_type_timelion/README.md | 10 + .../public/_generated_/chain.js | 1780 +++++++++++++++++ ...timelion_expression_input_helpers.test.ts} | 172 +- .../timelion_expression_input_helpers.ts | 13 +- tasks/config/peg.js | 4 + 6 files changed, 1843 insertions(+), 137 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/README.md create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js rename src/legacy/core_plugins/{timelion/public/directives/__tests__/timelion_expression_input_helpers.js => vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts} (70%) diff --git a/.eslintignore b/.eslintignore index c4fb806b6d394c..86a01b68ecab1f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,6 +9,7 @@ bower_components /built_assets /html_docs /src/plugins/data/common/es_query/kuery/ast/_generated_/** +/src/legacy/core_plugins/vis_type_timelion/public/_generated_/** src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data /src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts diff --git a/src/legacy/core_plugins/vis_type_timelion/README.md b/src/legacy/core_plugins/vis_type_timelion/README.md new file mode 100644 index 00000000000000..c306e03abf2c60 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/README.md @@ -0,0 +1,10 @@ +# Vis type Timelion + +# Generate a parser +If your grammar was changed in `public/chain.peg` you need to re-generate the static parser. You could use a grunt task: + +``` +grunt peg:timelion_chain +``` + +The generated parser will be appeared at `public/_generated_` folder, which is included in `.eslintignore` \ No newline at end of file diff --git a/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js new file mode 100644 index 00000000000000..f812b94238d43d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js @@ -0,0 +1,1780 @@ +module.exports = (function() { + "use strict"; + + /* + * Generated by PEG.js 0.9.0. + * + * http://pegjs.org/ + */ + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } + } + + peg$subclass(peg$SyntaxError, Error); + + function peg$parse(input) { + var options = arguments.length > 1 ? arguments[1] : {}, + parser = this, + + peg$FAILED = {}, + + peg$startRuleFunctions = { start: peg$parsestart }, + peg$startRuleFunction = peg$parsestart, + + peg$c0 = function(tree) { + return { + tree: tree.filter(function (o) {return o != null}), + functions: functions, + args: args, + variables: variables + } + }, + peg$c1 = ",", + peg$c2 = { type: "literal", value: ",", description: "\",\"" }, + peg$c3 = function(first, arg) {return arg}, + peg$c4 = function(first, rest) { + return [first].concat(rest); + }, + peg$c5 = "=", + peg$c6 = { type: "literal", value: "=", description: "\"=\"" }, + peg$c7 = function(name, value) { + var arg = { + type: 'namedArg', + name: name, + value: value, + location: simpleLocation(location()), + text: text() + }; + currentArgs.push(arg); + return arg; + }, + peg$c8 = function(value) { + var exception = { + type: 'incompleteArgument', + currentArgs: currentArgs, + currentFunction: currentFunction, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c9 = function(name) { + var exception = { + type: 'incompleteArgumentValue', + currentArgs: currentArgs, + currentFunction: currentFunction, + name: name, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c10 = function(element) {return element}, + peg$c11 = function(literal) { + var result = ltoo(literal); + result.location = simpleLocation(location()), + result.text = text(); + return result; + }, + peg$c12 = "$", + peg$c13 = { type: "literal", value: "$", description: "\"$\"" }, + peg$c14 = function(name) { + if (variables[name]) { + return variables[name]; + } else { + error('$' + name + ' is not defined') + } + }, + peg$c15 = function(name, value) { + variables[name] = value; + }, + peg$c16 = function(first, series) {return series}, + peg$c17 = function(first, rest) { + return [first].concat(rest) + }, + peg$c18 = /^[a-zA-Z]/, + peg$c19 = { type: "class", value: "[a-zA-Z]", description: "[a-zA-Z]" }, + peg$c20 = /^[.a-zA-Z0-9_\-]/, + peg$c21 = { type: "class", value: "[.a-zA-Z0-9_-]", description: "[.a-zA-Z0-9_-]" }, + peg$c22 = function(first, rest) { + currentFunction = first.join('') + rest.join(''); + currentArgs = []; + return currentFunction; + }, + peg$c23 = function(first, rest) { return first.join('') + rest.join('') }, + peg$c24 = { type: "other", description: "function" }, + peg$c25 = ".", + peg$c26 = { type: "literal", value: ".", description: "\".\"" }, + peg$c27 = "(", + peg$c28 = { type: "literal", value: "(", description: "\"(\"" }, + peg$c29 = ")", + peg$c30 = { type: "literal", value: ")", description: "\")\"" }, + peg$c31 = function(name, arg_list) { + var result = { + type: 'function', + function: name, + arguments: arg_list || [], + location: simpleLocation(location()), + text: text() + } + + result.arguments.forEach(function (arg) { + arg.function = name; + args.push(arg); + }) + + functions.push(result) + return result; + }, + peg$c32 = function(func) { + var exception = { + type: 'incompleteFunction', + function: func, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c33 = "@", + peg$c34 = { type: "literal", value: "@", description: "\"@\"" }, + peg$c35 = ":", + peg$c36 = { type: "literal", value: ":", description: "\":\"" }, + peg$c37 = function(plot, series) { + return { + type: 'reference', + plot: plot, + series: series + } + }, + peg$c38 = function(plot) { + return { + type: 'reference', + plot: plot + } + }, + peg$c39 = function(func, rest) {return {type: 'chain', chain: [func].concat(rest)}}, + peg$c40 = function(grouped, functions) { + var first = { + type: 'chainList', + list: grouped + } + first.label = text(); + + return {type: "chain", chain: [first].concat(functions)}; + }, + peg$c41 = { type: "other", description: "literal" }, + peg$c42 = "\"", + peg$c43 = { type: "literal", value: "\"", description: "\"\\\"\"" }, + peg$c44 = function(chars) { return chars.join(''); }, + peg$c45 = "'", + peg$c46 = { type: "literal", value: "'", description: "\"'\"" }, + peg$c47 = "true", + peg$c48 = { type: "literal", value: "true", description: "\"true\"" }, + peg$c49 = function() { return true; }, + peg$c50 = "false", + peg$c51 = { type: "literal", value: "false", description: "\"false\"" }, + peg$c52 = function() { return false; }, + peg$c53 = "null", + peg$c54 = { type: "literal", value: "null", description: "\"null\"" }, + peg$c55 = function() { return null; }, + peg$c56 = /^[^()"',= \t]/, + peg$c57 = { type: "class", value: "[^()\"',=\\ \\t]", description: "[^()\"',=\\ \\t]" }, + peg$c58 = function(string) { // this also matches numbers via Number() + var result = string.join(''); + // Sort of hacky, but PEG doesn't have backtracking so + // a number rule is hard to read, and performs worse + if (isNaN(Number(result))) return result; + return Number(result) + }, + peg$c59 = /^[ \t\r\n]/, + peg$c60 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, + peg$c61 = "\\", + peg$c62 = { type: "literal", value: "\\", description: "\"\\\\\"" }, + peg$c63 = function(sequence) { return sequence; }, + peg$c64 = /^[^"]/, + peg$c65 = { type: "class", value: "[^\"]", description: "[^\"]" }, + peg$c66 = /^[^']/, + peg$c67 = { type: "class", value: "[^']", description: "[^']" }, + peg$c68 = /^[0-9]/, + peg$c69 = { type: "class", value: "[0-9]", description: "[0-9]" }, + peg$c70 = function(digits) {return parseInt(digits.join(''))}, + + peg$currPos = 0, + peg$savedPos = 0, + peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }], + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description) { + throw peg$buildException( + null, + [{ type: "other", description: description }], + input.substring(peg$savedPos, peg$currPos), + peg$computeLocation(peg$savedPos, peg$currPos) + ); + } + + function error(message) { + throw peg$buildException( + message, + null, + input.substring(peg$savedPos, peg$currPos), + peg$computeLocation(peg$savedPos, peg$currPos) + ); + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos], + p, ch; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column, + seenCR: details.seenCR + }; + + while (p < pos) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos), + endPosDetails = peg$computePosDetails(endPos); + + return { + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildException(message, expected, found, location) { + function cleanupExpected(expected) { + var i = 1; + + expected.sort(function(a, b) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } else { + return 0; + } + }); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDescs = new Array(expected.length), + expectedDesc, foundDesc, i; + + for (i = 0; i < expected.length; i++) { + expectedDescs[i] = expected[i].description; + } + + expectedDesc = expected.length > 1 + ? expectedDescs.slice(0, -1).join(", ") + + " or " + + expectedDescs[expected.length - 1] + : expectedDescs[0]; + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + if (expected !== null) { + cleanupExpected(expected); + } + + return new peg$SyntaxError( + message !== null ? message : buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseseries(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c0(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsearg_list() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseargument(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseargument(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c3(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseargument(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c3(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c4(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseargument() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseargument_name(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s3 = peg$c5; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + s5 = peg$parsearg_type(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c7(s1, s5); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s2 = peg$c5; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + s4 = peg$parsearg_type(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c8(s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseargument_name(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s3 = peg$c5; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c9(s1); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsearg_type(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c10(s1); + } + s0 = s1; + } + } + } + + return s0; + } + + function peg$parsearg_type() { + var s0, s1; + + s0 = peg$parsevariable_get(); + if (s0 === peg$FAILED) { + s0 = peg$parseseries_type(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseliteral(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c11(s1); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parsevariable_get() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseargument_name(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c14(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsevariable_set() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseargument_name(); + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s4 = peg$c5; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parsespace(); + if (s5 === peg$FAILED) { + s5 = null; + } + if (s5 !== peg$FAILED) { + s6 = peg$parsearg_type(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c15(s2, s6); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseseries_type() { + var s0; + + s0 = peg$parsevariable_set(); + if (s0 === peg$FAILED) { + s0 = peg$parsevariable_get(); + if (s0 === peg$FAILED) { + s0 = peg$parsegroup(); + if (s0 === peg$FAILED) { + s0 = peg$parsechain(); + if (s0 === peg$FAILED) { + s0 = peg$parsereference(); + } + } + } + } + + return s0; + } + + function peg$parseseries() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseseries_type(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseseries_type(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c16(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseseries_type(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c16(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c17(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefunction_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c22(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseargument_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c23(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefunction() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s2 = peg$c25; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsefunction_name(); + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 40) { + s5 = peg$c27; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parsearg_list(); + if (s7 === peg$FAILED) { + s7 = null; + } + if (s7 !== peg$FAILED) { + s8 = peg$parsespace(); + if (s8 === peg$FAILED) { + s8 = null; + } + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s9 = peg$c29; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s9 !== peg$FAILED) { + s10 = peg$parsespace(); + if (s10 === peg$FAILED) { + s10 = null; + } + if (s10 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c31(s3, s7); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c25; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsefunction_name(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c32(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c24); } + } + + return s0; + } + + function peg$parsereference() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c33; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseinteger(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c35; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c36); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseinteger(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c37(s2, s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c33; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseinteger(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c38(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parsechain() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsefunction(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsefunction(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsefunction(); + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c39(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsegroup() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c27; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseseries(); + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s5 = peg$c29; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parsefunction(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parsefunction(); + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c40(s3, s6); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseliteral() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 34) { + s1 = peg$c42; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsedq_char(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsedq_char(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s3 = peg$c42; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c44(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 39) { + s1 = peg$c45; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesq_char(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesq_char(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s3 = peg$c45; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c44(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c47) { + s1 = peg$c47; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c48); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c49(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c50) { + s1 = peg$c50; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c52(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c53) { + s1 = peg$c53; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c54); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c55(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = []; + if (peg$c56.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c57); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c56.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c57); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c58(s1); + } + s0 = s1; + } + } + } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + + return s0; + } + + function peg$parsespace() { + var s0, s1; + + s0 = []; + if (peg$c59.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + if (s1 !== peg$FAILED) { + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$c59.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + } + } else { + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsedq_char() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c61; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s2 = peg$c42; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 92) { + s2 = peg$c61; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c63(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + if (peg$c64.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c65); } + } + } + + return s0; + } + + function peg$parsesq_char() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c61; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s2 = peg$c45; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 92) { + s2 = peg$c61; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c63(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + if (peg$c66.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c67); } + } + } + + return s0; + } + + function peg$parseinteger() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + if (peg$c68.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c69); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c68.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c69); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c70(s1); + } + s0 = s1; + + return s0; + } + + + function ltoo (literal) { + return {type: 'literal', value: literal} + } + + function simpleLocation (location) { + // Returns an object representing the position of the function within the expression, + // demarcated by the position of its first character and last character. We calculate these values + // using the offset because the expression could span multiple lines, and we don't want to deal + // with column and line values. + return { + min: location.start.offset, + max: location.end.offset + } + } + + var currentFunction; + var currentArgs = []; + + var functions = []; + var args = []; + var variables = {}; + + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail({ type: "end", description: "end of input" }); + } + + throw peg$buildException( + null, + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } + } + + return { + SyntaxError: peg$SyntaxError, + parse: peg$parse + }; +})(); \ No newline at end of file diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts similarity index 70% rename from src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts index ea2d44bcaefe07..18a0c0872dc031 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts @@ -17,19 +17,16 @@ * under the License. */ -import expect from '@kbn/expect'; -import PEG from 'pegjs'; -import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; -import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; -import { - setIndexPatterns, - setSavedObjectsClient, -} from '../../../../vis_type_timelion/public/helpers/plugin_services'; +import { SUGGESTION_TYPE, suggest } from './timelion_expression_input_helpers'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; +import { setIndexPatterns, setSavedObjectsClient } from '../helpers/plugin_services'; +import { IndexPatterns } from 'src/plugins/data/public'; +import { SavedObjectsClient } from 'kibana/public'; +import { ITimelionFunction } from '../../common/types'; describe('Timelion expression suggestions', () => { - setIndexPatterns({}); - setSavedObjectsClient({}); + setIndexPatterns({} as IndexPatterns); + setSavedObjectsClient({} as SavedObjectsClient); const argValueSuggestions = getArgValueSuggestions(); @@ -45,17 +42,13 @@ describe('Timelion expression suggestions', () => { suggestions: [{ name: 'value1' }], }, ], - }; + } as ITimelionFunction; const myFunc2 = { name: 'myFunc2', chainable: false, args: [{ name: 'argA' }, { name: 'argAB' }, { name: 'argABC' }], - }; + } as ITimelionFunction; const functionList = [func1, myFunc2]; - let Parser; - beforeEach(function() { - Parser = PEG.generate(grammar); - }); describe('parse exception', () => { describe('incompleteFunction', () => { @@ -65,17 +58,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [func1, myFunc2], - location: { - min: 0, - max: 1, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); it('should filter function suggestions by function name', async () => { @@ -84,17 +72,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [myFunc2], - location: { - min: 0, - max: 4, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); }); @@ -106,17 +89,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 11, - max: 12, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -126,17 +104,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 10, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -146,17 +119,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 25, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -166,17 +134,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }], - location: { - min: 7, - max: 8, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -186,17 +149,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argABC' }], - location: { - min: 24, - max: 25, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); }); @@ -208,17 +166,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 11, - max: 11, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); @@ -228,17 +181,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'value1' }], - location: { - min: 11, - max: 11, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); }); @@ -252,17 +200,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [func1], - location: { - min: 0, - max: 8, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); }); @@ -275,17 +218,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 9, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should not provide argument suggestions for argument that is all ready set in function def', async () => { @@ -294,18 +232,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions.type).to.equal(SUGGESTION_TYPE.ARGUMENTS); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argABC' }], - location: { - min: 24, - max: 24, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should filter argument suggestions by argument name', async () => { @@ -314,17 +246,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argAB' }, { name: 'argABC' }], - location: { - min: 9, - max: 14, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should not show first argument for chainable functions', async () => { @@ -333,17 +260,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }], - location: { - min: 7, - max: 7, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); }); @@ -354,17 +276,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 14, - max: 16, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); @@ -374,17 +291,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'value1' }], - location: { - min: 13, - max: 16, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index 5aa05fb16466bb..d7818680c9543e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -18,18 +18,17 @@ */ import { get, startsWith } from 'lodash'; -import PEG from 'pegjs'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { i18n } from '@kbn/i18n'; +import { Parser } from 'pegjs'; + // @ts-ignore -import grammar from 'raw-loader!../chain.peg'; +import { parse } from '../_generated_/chain'; -import { i18n } from '@kbn/i18n'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; -const Parser = PEG.generate(grammar); - export enum SUGGESTION_TYPE { ARGUMENTS = 'arguments', ARGUMENT_VALUE = 'argument_value', @@ -57,7 +56,7 @@ function getArgumentsHelp( } async function extractSuggestionsFromParsedResult( - result: ReturnType, + result: ReturnType, cursorPosition: number, functionList: ITimelionFunction[], argValueSuggestions: ArgValueSuggestions @@ -141,7 +140,7 @@ export async function suggest( argValueSuggestions: ArgValueSuggestions ) { try { - const result = await Parser.parse(expression); + const result = await parse(expression); return await extractSuggestionsFromParsedResult( result, diff --git a/tasks/config/peg.js b/tasks/config/peg.js index 0c57c6139d2984..1d8667840faba0 100644 --- a/tasks/config/peg.js +++ b/tasks/config/peg.js @@ -25,4 +25,8 @@ module.exports = { allowedStartRules: ['start', 'Literal'], }, }, + timelion_chain: { + src: 'src/legacy/core_plugins/vis_type_timelion/public/chain.peg', + dest: 'src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js', + }, }; From be31198d21c1693c6d67a4cc47726325ca192a45 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 21 Jan 2020 15:55:54 +0100 Subject: [PATCH 17/59] Re-enable Kerberos + anonymous access test. (#55377) --- .../kerberos_api_integration/apis/security/kerberos_login.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index bd35f21d8f428b..0346da334d2f2d 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -344,8 +344,7 @@ export default function({ getService }: FtrProviderContext) { }); }); - // FAILING: https://github.com/elastic/kibana/issues/52969 - describe.skip('API access with missing access token document or expired refresh token.', () => { + describe('API access with missing access token document or expired refresh token.', () => { let sessionCookie: Cookie; beforeEach(async () => { From ce286f543e4bd4d72ec7ffefac3a410cac2eaf0f Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 21 Jan 2020 15:25:58 +0000 Subject: [PATCH 18/59] [ML] Adding missing job groups to recognizer wizard (#55392) --- .../application/jobs/new_job/recognize/page.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 141ed5d1bbb8ff..c4a96d9e373c8d 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -77,6 +77,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const [kibanaObjects, setKibanaObjects] = useState({}); const [saveState, setSaveState] = useState(SAVE_STATE.NOT_SAVED); const [resultsUrl, setResultsUrl] = useState(''); + const [existingGroups, setExistingGroups] = useState(existingGroupIds); // #endregion const { @@ -109,6 +110,10 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { setKibanaObjects(kibanaObjectsResult); setSaveState(SAVE_STATE.NOT_SAVED); + + // mix existing groups from the server with the groups used across all jobs in the module. + const moduleGroups = [...response.jobs.map(j => j.config.groups || [])].flat(); + setExistingGroups([...new Set([...existingGroups, ...moduleGroups])]); } catch (e) { // eslint-disable-next-line no-console console.error(e); @@ -222,6 +227,12 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { ...jobOverrides, [job.job_id as string]: job, }); + if (job.groups !== undefined) { + // add newly added jobs to the list of existing groups + // for use when editing other jobs in the module + const groups = [...new Set([...existingGroups, ...job.groups])]; + setExistingGroups(groups); + } }; const isFormVisible = [SAVE_STATE.NOT_SAVED, SAVE_STATE.SAVING].includes(saveState); @@ -304,7 +315,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { jobs={jobs} jobPrefix={jobPrefix} saveState={saveState} - existingGroupIds={existingGroupIds} + existingGroupIds={existingGroups} jobOverrides={jobOverrides} onJobOverridesChange={onJobOverridesChange} /> From 2bf111c50fc211454a6f658b488323e22a8ad4e3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Jan 2020 10:58:57 -0500 Subject: [PATCH 19/59] [Uptime] Fix flaky uptime overview page test (#54767) * Fix flaky uptime overview page test. * Increase timeout for url checks. * Prefer standard `retry.try` to custom retry implementation. * Remove unneeded symbol. * Remove unnecessary type annotation. Co-authored-by: Elastic Machine --- x-pack/test/functional/apps/uptime/overview.ts | 9 +++------ x-pack/test/functional/page_objects/uptime_page.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/test/functional/apps/uptime/overview.ts b/x-pack/test/functional/apps/uptime/overview.ts index ba701da6e517dd..2ef6a381a6a30e 100644 --- a/x-pack/test/functional/apps/uptime/overview.ts +++ b/x-pack/test/functional/apps/uptime/overview.ts @@ -50,13 +50,11 @@ export default ({ getPageObjects }: FtrProviderContext) => { ]); }); - // flakey see https://github.com/elastic/kibana/issues/54527 - it.skip('pagination is cleared when filter criteria changes', async () => { + it('pagination is cleared when filter criteria changes', async () => { await pageObjects.uptime.goToUptimePageAndSetDateRange(DEFAULT_DATE_START, DEFAULT_DATE_END); await pageObjects.uptime.changePage('next'); // there should now be pagination data in the URL - const contains = await pageObjects.uptime.pageUrlContains('pagination'); - expect(contains).to.be(true); + await pageObjects.uptime.pageUrlContains('pagination'); await pageObjects.uptime.pageHasExpectedIds([ '0010-down', '0011-up', @@ -71,8 +69,7 @@ export default ({ getPageObjects }: FtrProviderContext) => { ]); await pageObjects.uptime.setStatusFilter('up'); // ensure that pagination is removed from the URL - const doesNotContain = await pageObjects.uptime.pageUrlContains('pagination'); - expect(doesNotContain).to.be(false); + await pageObjects.uptime.pageUrlContains('pagination', false); await pageObjects.uptime.pageHasExpectedIds([ '0000-intermittent', '0001-up', diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index f04f96148583fa..2ae0ea38c957bb 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'timePicker']); const uptimeService = getService('uptime'); + const retry = getService('retry'); return new (class UptimePage { public async goToUptimePageAndSetDateRange( @@ -51,8 +53,10 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo await Promise.all(monitorIdsToCheck.map(id => uptimeService.monitorPageLinkExists(id))); } - public async pageUrlContains(value: string) { - return await uptimeService.urlContains(value); + public async pageUrlContains(value: string, expected: boolean = true) { + retry.try(async () => { + expect(await uptimeService.urlContains(value)).to.eql(expected); + }); } public async changePage(direction: 'next' | 'prev') { From b0af1bf95c26a03135a382c0c758b54782e514fa Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Tue, 21 Jan 2020 09:19:08 -0700 Subject: [PATCH 20/59] Clear core loading indicator just before UI is rendered (#55242) Co-authored-by: Elastic Machine --- src/core/public/core_system.test.ts | 7 +++---- src/core/public/core_system.ts | 15 ++++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 1ee41fe64418ec..94fa74f4bd861f 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -429,15 +429,14 @@ describe('Notifications targetDomElement', () => { rootDomElement, }); - let targetDomElementParentInStart: HTMLElement | null; + let targetDomElementInStart: HTMLElement | null; MockNotificationsService.start.mockImplementation(({ targetDomElement }): any => { - expect(targetDomElement.parentElement).not.toBeNull(); - targetDomElementParentInStart = targetDomElement.parentElement; + targetDomElementInStart = targetDomElement; }); // Starting the core system should pass the targetDomElement as a child of the rootDomElement await core.setup(); await core.start(); - expect(targetDomElementParentInStart!).toBe(rootDomElement); + expect(targetDomElementInStart!.parentElement).toBe(rootDomElement); }); }); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 97bd49416f7052..be69cacdd271a7 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -222,13 +222,6 @@ export class CoreSystem { const notificationsTargetDomElement = document.createElement('div'); const overlayTargetDomElement = document.createElement('div'); - // ensure the rootDomElement is empty - this.rootDomElement.textContent = ''; - this.rootDomElement.classList.add('coreSystemRootDomElement'); - this.rootDomElement.appendChild(coreUiTargetDomElement); - this.rootDomElement.appendChild(notificationsTargetDomElement); - this.rootDomElement.appendChild(overlayTargetDomElement); - const overlays = this.overlay.start({ i18n, targetDomElement: overlayTargetDomElement, @@ -276,6 +269,14 @@ export class CoreSystem { }; const plugins = await this.plugins.start(core); + + // ensure the rootDomElement is empty + this.rootDomElement.textContent = ''; + this.rootDomElement.classList.add('coreSystemRootDomElement'); + this.rootDomElement.appendChild(coreUiTargetDomElement); + this.rootDomElement.appendChild(notificationsTargetDomElement); + this.rootDomElement.appendChild(overlayTargetDomElement); + const rendering = this.rendering.start({ application, chrome, From ce2930ec5154ef4ccba8420744a522225cd24210 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 21 Jan 2020 10:46:56 -0600 Subject: [PATCH 21/59] De-angularize vis tooltips (#54954) * Remove angular dependencey from vis/tooltip * Move tooltip logic into vislib * Remove and fix all ngMock refs in vislib tests * Add numeral to renovate config * Add vis_type_vislib to codeowners * Move vis_legend into vislib and fix errors * vis_type_vislib/public imports to be only top-level --- .github/CODEOWNERS | 1 + .i18nrc.json | 4 +- package.json | 1 + renovate.json5 | 8 + .../public/components/region_map_options.tsx | 6 +- .../public/components/tile_map_options.tsx | 2 +- .../components/wms_internal_options.tsx | 2 +- .../public/components/wms_options.tsx | 2 +- .../public/settings_options.tsx | 2 +- .../public/components/metric_vis_options.tsx | 4 +- .../vis_type_metric/public/metric_vis_fn.ts | 2 +- .../vis_type_metric/public/metric_vis_type.ts | 2 +- .../vis_type_metric/public/types.ts | 3 +- .../public/components/table_vis_options.tsx | 6 +- .../public/components/tag_cloud_options.tsx | 2 +- .../vis_type_vislib/public/area.ts | 14 +- .../components/common/basic_options.tsx | 4 +- .../public/components/common/color_ranges.tsx | 2 +- .../public/components/common/color_schema.tsx | 8 +- .../public/components/common/range.tsx | 2 +- .../components/common/truncate_labels.tsx | 2 +- .../components/options/gauge/labels_panel.tsx | 10 +- .../components/options/gauge/ranges_panel.tsx | 14 +- .../components/options/gauge/style_panel.tsx | 6 +- .../components/options/heatmap/index.tsx | 23 +- .../options/heatmap/labels_panel.tsx | 10 +- .../category_axis_panel.test.tsx.snap | 2 +- .../__snapshots__/label_options.test.tsx.snap | 2 +- .../value_axes_panel.test.tsx.snap | 2 +- .../metrics_axes/category_axis_panel.tsx | 6 +- .../options/metrics_axes/chart_options.tsx | 10 +- .../metrics_axes/custom_extents_options.tsx | 15 +- .../options/metrics_axes/label_options.tsx | 29 +- .../options/metrics_axes/line_options.tsx | 8 +- .../options/metrics_axes/series_panel.tsx | 4 +- .../options/metrics_axes/value_axes_panel.tsx | 8 +- .../metrics_axes/value_axis_options.tsx | 14 +- .../options/metrics_axes/y_extents.tsx | 20 +- .../public/components/options/pie.tsx | 12 +- .../options/point_series/grid_panel.tsx | 10 +- .../options/point_series/point_series.tsx | 8 +- .../options/point_series/threshold_panel.tsx | 40 +- .../vis_type_vislib/public/gauge.ts | 8 +- .../vis_type_vislib/public/goal.ts | 8 +- .../vis_type_vislib/public/heatmap.ts | 12 +- .../vis_type_vislib/public/histogram.ts | 14 +- .../vis_type_vislib/public/horizontal_bar.ts | 14 +- .../vis_type_vislib/public/index.ts | 14 + .../vis_type_vislib/public/legacy.ts | 14 - .../vis_type_vislib/public/legacy_imports.ts | 5 +- .../vis_type_vislib/public/line.ts | 14 +- .../vis_type_vislib/public/pie.ts | 10 +- .../vis_type_vislib/public/pie_fn.ts | 2 +- .../vis_type_vislib/public/plugin.ts | 4 - .../public/utils/collections.ts | 60 +-- .../public/utils/common_config.tsx | 6 +- .../vis_type_vislib/public/vis_controller.tsx | 6 +- .../public/vis_type_vislib_vis_fn.ts | 2 +- .../components}/_tooltip_formatter.js | 13 +- .../__tests__/components/heatmap_color.js | 3 - .../vislib/__tests__/components/labels.js | 74 ++-- .../__tests__/components}/positioning.js | 3 +- .../public/vislib/__tests__/index.js | 11 +- .../public/vislib/__tests__/lib/axis/axis.js | 60 ++- .../public/vislib/__tests__/lib/axis_title.js | 102 +++-- .../vislib/__tests__/lib/chart_title.js | 54 ++- .../public/vislib/__tests__/lib/data.js | 28 +- .../public/vislib/__tests__/lib/dispatch.js | 90 ++--- .../vislib/__tests__/lib/error_handler.js | 11 +- .../__tests__/lib/fixtures/_vis_fixture.js | 51 ++- .../vislib/__tests__/lib/handler/handler.js | 27 +- .../vislib/__tests__/lib/layout/layout.js | 28 +- .../__tests__/lib/layout/layout_types.js | 11 +- .../lib/layout/splits/column_chart/splits.js | 103 +++-- .../lib/layout/splits/gauge_chart/splits.js | 43 +- .../lib/layout/types/column_layout.js | 19 +- .../__tests__/lib/types/point_series.js | 7 +- .../public/vislib/__tests__/lib/vis_config.js | 40 +- .../public/vislib/__tests__/lib/x_axis.js | 54 ++- .../public/vislib/__tests__/lib/y_axis.js | 23 +- .../public/vislib/__tests__/vis.js | 38 +- .../__tests__/visualizations/area_chart.js | 73 ++-- .../vislib/__tests__/visualizations/chart.js | 42 +- .../__tests__/visualizations/column_chart.js | 86 ++-- .../__tests__/visualizations/gauge_chart.js | 24 +- .../__tests__/visualizations/heatmap_chart.js | 31 +- .../__tests__/visualizations/line_chart.js | 70 ++-- .../__tests__/visualizations/pie_chart.js | 90 ++--- .../__tests__/visualizations/time_marker.js | 41 +- .../__tests__/visualizations/vis_types.js | 13 +- .../vis_type_vislib/public/vislib/_index.scss | 3 + .../public/vislib}/_vislib_vis_type.scss | 3 + .../__snapshots__/legend.test.tsx.snap} | 0 .../vislib/components/legend/_index.scss | 1 + .../vislib/components/legend/_legend.scss} | 9 +- .../public/vislib/components/legend}/index.ts | 2 +- .../vislib/components/legend/legend.test.tsx} | 6 +- .../vislib/components/legend/legend.tsx} | 12 +- .../vislib/components/legend/legend_item.tsx} | 10 +- .../vislib/components/legend}/models.ts | 0 .../vislib/components/legend}/pie_utils.ts | 0 .../components/tooltip/_collect_branch.js | 0 .../tooltip/_collect_branch.test.js | 0 .../_hierarchical_tooltip_formatter.js | 93 +++++ .../vislib}/components/tooltip/_index.scss | 0 .../tooltip/_pointseries_tooltip_formatter.js | 79 ++++ .../vislib}/components/tooltip/_tooltip.scss | 3 +- .../vislib}/components/tooltip/index.js | 4 +- .../components/tooltip/position_tooltip.js | 0 .../vislib}/components/tooltip/tooltip.js | 11 +- .../public/vislib/lib/chart_title.js | 2 +- .../public/vislib/lib/types/point_series.js | 2 +- .../public/vislib/visualizations/_chart.js | 11 +- .../vislib/visualizations/point_series.js | 18 +- src/legacy/ui/public/vis/_index.scss | 2 - .../ui/public/vis/components/_index.scss | 1 - .../tooltip/_hierarchical_tooltip.html | 28 -- .../_hierarchical_tooltip_formatter.js | 82 ---- .../tooltip/_pointseries_tooltip.html | 12 - .../tooltip/_pointseries_tooltip_formatter.js | 89 ----- .../ui/public/vis/vis_types/_index.scss | 2 - .../translations/translations/ja-JP.json | 372 +++++++++--------- .../translations/translations/zh-CN.json | 372 +++++++++--------- yarn.lock | 5 + 124 files changed, 1415 insertions(+), 1707 deletions(-) rename src/legacy/{ui/public/vis/components/tooltip/__tests__ => core_plugins/vis_type_vislib/public/vislib/__tests__/components}/_tooltip_formatter.js (88%) rename src/legacy/{ui/public/vis/components/tooltip/__tests__ => core_plugins/vis_type_vislib/public/vislib/__tests__/components}/positioning.js (99%) rename src/legacy/{ui/public/vis/vis_types => core_plugins/vis_type_vislib/public/vislib}/_vislib_vis_type.scss (99%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap => core_plugins/vis_type_vislib/public/vislib/components/legend/__snapshots__/legend.test.tsx.snap} (100%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss rename src/legacy/{ui/public/vis/vis_types/_vislib_vis_legend.scss => core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss} (85%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend => core_plugins/vis_type_vislib/public/vislib/components/legend}/index.ts (94%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx => core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx} (96%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx => core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx} (93%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx => core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx} (92%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend => core_plugins/vis_type_vislib/public/vislib/components/legend}/models.ts (100%) rename src/legacy/{ui/public/vis/vis_types/vislib_vis_legend => core_plugins/vis_type_vislib/public/vislib/components/legend}/pie_utils.ts (100%) rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/_collect_branch.js (100%) rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/_collect_branch.test.js (100%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/_index.scss (100%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/_tooltip.scss (97%) rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/index.js (80%) rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/position_tooltip.js (100%) rename src/legacy/{ui/public/vis => core_plugins/vis_type_vislib/public/vislib}/components/tooltip/tooltip.js (97%) delete mode 100644 src/legacy/ui/public/vis/components/_index.scss delete mode 100644 src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html delete mode 100644 src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js delete mode 100644 src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html delete mode 100644 src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js delete mode 100644 src/legacy/ui/public/vis/vis_types/_index.scss diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed5721e8756e88..7276a726fd6d1f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/legacy/core_plugins/metrics/ @elastic/kibana-app +/src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/plugins/home/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app diff --git a/.i18nrc.json b/.i18nrc.json index 73acf92cda1491..907310b32e35cc 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -21,7 +21,6 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "kbnVislibVisTypes": "src/legacy/core_plugins/vis_type_vislib", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", @@ -41,6 +40,7 @@ "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": "src/legacy/core_plugins/vis_type_timeseries", "visTypeVega": "src/legacy/core_plugins/vis_type_vega", + "visTypeVislib": "src/legacy/core_plugins/vis_type_vislib", "visualizations": [ "src/plugins/visualizations", "src/legacy/core_plugins/visualizations" @@ -50,4 +50,4 @@ "src/legacy/ui/ui_render/ui_render_mixin.js" ], "translations": [] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 430ab9e1ba77d7..8b96fcc9d396e7 100644 --- a/package.json +++ b/package.json @@ -347,6 +347,7 @@ "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", + "@types/numeral": "^0.0.26", "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", diff --git a/renovate.json5 b/renovate.json5 index 5af62d0acef854..6764ed38ba4cf0 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -657,6 +657,14 @@ '@types/nodemailer', ], }, + { + groupSlug: 'numeral', + groupName: 'numeral related packages', + packageNames: [ + 'numeral', + '@types/numeral', + ], + }, { groupSlug: 'object-hash', groupName: 'object-hash related packages', diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 73fe07ec60102d..287fb87e735a25 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -24,11 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { - NumberInputOption, - SelectOption, - SwitchOption, -} from '../../../vis_type_vislib/public/components'; +import { NumberInputOption, SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index e57cea8467d124..4ab9f95ee4c3c2 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -27,7 +27,7 @@ import { RangeOption, SelectOption, SwitchOption, -} from '../../../vis_type_vislib/public/components'; +} from '../../../vis_type_vislib/public'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx index 2989f6ce7ebd58..b81667400303d5 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TextInputOption } from '../../../vis_type_vislib/public/components'; +import { TextInputOption } from '../../../vis_type_vislib/public'; import { WMSOptions } from '../types'; interface WmsInternalOptions { diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index d9dca5afd73776..a0b7a0a844f55f 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { Vis } from 'ui/vis'; import { RegionMapVisParams } from '../../../region_map/public/types'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsInternalOptions } from './wms_internal_options'; import { WMSOptions, TileMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index 125577815c207b..18852b549b1ed0 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { RangeOption, SwitchOption } from '../../vis_type_vislib/public/components'; +import { RangeOption, SwitchOption } from '../../vis_type_vislib/public'; import { MarkdownVisParams } from './types'; function SettingsOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 032f66d92624cc..e144c055d80238 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -31,13 +31,13 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from '../legacy_imports'; import { + ColorModes, ColorRanges, ColorSchemaOptions, SwitchOption, RangeOption, SetColorSchemaOptionsValue, -} from '../../../vis_type_vislib/public/components'; -import { ColorModes } from '../../../vis_type_vislib/public/utils/collections'; +} from '../../../vis_type_vislib/public'; import { MetricVisParam, VisParams } from '../types'; import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 45110ca1130030..4ebd8bf97c55a0 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -27,7 +27,7 @@ import { Render, Style, } from '../../../../plugins/expressions/public'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; +import { ColorModes } from '../../vis_type_vislib/public'; import { visType, DimensionsVisParam, VisParams } from './types'; export type Context = KibanaDatatable; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index 0d9019ee0579c1..ee7ead0b7331b4 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -21,8 +21,8 @@ import { i18n } from '@kbn/i18n'; import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; +import { ColorModes } from '../../vis_type_vislib/public'; import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; export const metricVisTypeDefinition = { name: 'metric', diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index 71c1c12b4f8f02..34cb1b209a3aed 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -20,8 +20,7 @@ import { ColorSchemas } from './legacy_imports'; import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../visualizations/public'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; -import { Labels, Style } from '../../vis_type_vislib/public/types'; +import { ColorModes, Labels, Style } from '../../vis_type_vislib/public'; export const visType = 'metric'; diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index 33d7480de5a8e3..529439a8006823 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -24,11 +24,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { tabifyGetColumns, VisOptionsProps } from '../legacy_imports'; -import { - NumberInputOption, - SwitchOption, - SelectOption, -} from '../../../vis_type_vislib/public/components/common'; +import { NumberInputOption, SwitchOption, SelectOption } from '../../../vis_type_vislib/public'; import { TableVisParams } from '../types'; import { totalAggregations, isAggConfigNumeric } from './utils'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index c500b5d888b053..76117c8b6b3987 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { ValidatedDualRange } from 'ui/validated_range'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { TagCloudVisParams } from '../types'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/area.ts b/src/legacy/core_plugins/vis_type_vislib/public/area.ts index 9484ddc16fe625..a96fb19a9321ca 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/area.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/area.ts @@ -42,9 +42,9 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'area', - title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), + title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', - description: i18n.translate('kbnVislibVisTypes.area.areaDescription', { + description: i18n.translate('visTypeVislib.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart', }), visualization: createVislibVisController(deps), @@ -136,7 +136,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { + title: i18n.translate('visTypeVislib.area.metricsTitle', { defaultMessage: 'Y-axis', }), aggFilter: ['!geo_centroid', '!geo_bounds'], @@ -146,7 +146,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { + title: i18n.translate('visTypeVislib.area.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -156,7 +156,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { + title: i18n.translate('visTypeVislib.area.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -166,7 +166,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.area.groupTitle', { + title: i18n.translate('visTypeVislib.area.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -176,7 +176,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.area.splitTitle', { + title: i18n.translate('visTypeVislib.area.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx index 81174d63060e5c..229945621fe76d 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx @@ -37,7 +37,7 @@ function BasicOptions({ return ( <> ({ setValue={setValue} /> @@ -79,11 +79,11 @@ function ColorSchemaOptions({ disabled={disabled} helpText={ showHelpText && - i18n.translate('kbnVislibVisTypes.controls.colorSchema.howToChangeColorsDescription', { + i18n.translate('visTypeVislib.controls.colorSchema.howToChangeColorsDescription', { defaultMessage: 'Individual colors can be changed in the legend.', }) } - label={i18n.translate('kbnVislibVisTypes.controls.colorSchema.colorSchemaLabel', { + label={i18n.translate('visTypeVislib.controls.colorSchema.colorSchemaLabel', { defaultMessage: 'Color schema', })} labelAppend={isCustomColors && resetColorsButton} @@ -95,7 +95,7 @@ function ColorSchemaOptions({ ({ const [stateValue, setStateValue] = useState(value); const [isValidState, setIsValidState] = useState(true); - const error = i18n.translate('kbnVislibVisTypes.controls.rangeErrorMessage', { + const error = i18n.translate('visTypeVislib.controls.rangeErrorMessage', { defaultMessage: 'Values must be on or between {min} and {max}', values: { min, max }, }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx index 81772107bc729c..2f0cb701848d0c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx @@ -33,7 +33,7 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe return (

@@ -39,7 +39,7 @@ function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInter

@@ -76,10 +76,10 @@ function RangesPanel({

@@ -44,7 +44,7 @@ function StylePanel({ aggs, setGaugeValue, stateParams, vis }: GaugeOptionsInter ) {

@@ -78,13 +78,13 @@ function HeatmapOptions(props: VisOptionsProps) { ) {

@@ -114,7 +114,7 @@ function HeatmapOptions(props: VisOptionsProps) { ) { /> ) { ) { data-test-subj="heatmapColorsNumber" disabled={stateParams.setColorRange} isInvalid={isColorsNumberInvalid} - label={i18n.translate('kbnVislibVisTypes.controls.heatmapOptions.colorsNumberLabel', { + label={i18n.translate('visTypeVislib.controls.heatmapOptions.colorsNumberLabel', { defaultMessage: 'Number of colors', })} max={10} @@ -160,7 +159,7 @@ function HeatmapOptions(props: VisOptionsProps) {

@@ -73,7 +73,7 @@ function LabelsPanel({ valueAxis, setValue }: LabelsPanelProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap index d88654cfdc0c43..124b7d7004c0df 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap @@ -10,7 +10,7 @@ exports[`CategoryAxisPanel component should init with the default set of props 1

diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap index 027c074176ef85..8972cdbd82fc3a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap @@ -11,7 +11,7 @@ exports[`LabelOptions component should init with the default set of props 1`] =

diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap index 8d20765fe35918..f589a69eecbc3b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap @@ -18,7 +18,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`]

diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx index b83508f3f08964..a698d70d972324 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx @@ -62,7 +62,7 @@ function CategoryAxisPanel(props: CategoryAxisPanelProps) {

@@ -70,7 +70,7 @@ function CategoryAxisPanel(props: CategoryAxisPanelProps) {

@@ -73,12 +73,9 @@ function LabelOptions({ stateParams, setValue, axis, axesName, index }: LabelOpt

@@ -57,7 +57,7 @@ function SeriesPanel(props: SeriesPanelProps) { buttonContent={chart.data.label} buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" aria-label={i18n.translate( - 'kbnVislibVisTypes.controls.pointSeries.seriesAccordionAriaLabel', + 'visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel', { defaultMessage: 'Toggle {agg} options', values: { agg: chart.data.label }, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx index eb0ab4333af599..b94f5ebbcce44f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx @@ -60,7 +60,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const removeButtonTooltip = useMemo( () => - i18n.translate('kbnVislibVisTypes.controls.pointSeries.valueAxes.removeButtonTooltip', { + i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip', { defaultMessage: 'Remove Y-axis', }), [] @@ -83,7 +83,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const addButtonTooltip = useMemo( () => - i18n.translate('kbnVislibVisTypes.controls.pointSeries.valueAxes.addButtonTooltip', { + i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip', { defaultMessage: 'Add Y-axis', }), [] @@ -111,7 +111,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) {

@@ -142,7 +142,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { buttonClassName="eui-textTruncate" buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" aria-label={i18n.translate( - 'kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', + 'visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', { defaultMessage: 'Toggle {axisName} options', values: { axisName: axis.name }, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index b4ea4cb42ee60c..5daea193b06c1b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -119,7 +119,7 @@ function ValueAxisOptions(props: ValueAxisOptionsParams) { return ( <> ) {

) {

) { setValue={setLabels} /> ) { setValue={setLabels} />

@@ -72,14 +72,14 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps)

@@ -46,7 +46,7 @@ function PointSeriesOptions(props: ValidationVisOptionsProps) {vis.hasSchemaAgg('segment', 'date_histogram') ? ( ) /> ) : ( ) {vis.type.type === 'histogram' && (

@@ -63,7 +63,7 @@ function ThresholdPanel({ diff --git a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts index 5dcc8ad16918d0..f235ed4bb04e40 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts @@ -56,9 +56,9 @@ export interface GaugeVisParams { export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'gauge', - title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), + title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', - description: i18n.translate('kbnVislibVisTypes.gauge.gaugeDescription', { + description: i18n.translate('visTypeVislib.gauge.gaugeDescription', { defaultMessage: "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.", }), @@ -116,7 +116,7 @@ export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.gauge.metricTitle', { defaultMessage: 'Metric' }), + title: i18n.translate('visTypeVislib.gauge.metricTitle', { defaultMessage: 'Metric' }), min: 1, aggFilter: [ '!std_dev', @@ -134,7 +134,7 @@ export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.gauge.groupTitle', { + title: i18n.translate('visTypeVislib.gauge.groupTitle', { defaultMessage: 'Split group', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts index 302d5f6393ef90..94262629669bec 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts @@ -27,9 +27,9 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'goal', - title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), + title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }), icon: 'visGoal', - description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { + description: i18n.translate('visTypeVislib.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.', }), visualization: createVislibVisController(deps), @@ -80,7 +80,7 @@ export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.goal.metricTitle', { defaultMessage: 'Metric' }), + title: i18n.translate('visTypeVislib.goal.metricTitle', { defaultMessage: 'Metric' }), min: 1, aggFilter: [ '!std_dev', @@ -98,7 +98,7 @@ export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.goal.groupTitle', { + title: i18n.translate('visTypeVislib.goal.groupTitle', { defaultMessage: 'Split group', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts index eb5f84b409838a..470e978027ca2b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts @@ -41,9 +41,9 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibP export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'heatmap', - title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), + title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'visHeatmap', - description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { + description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix', }), visualization: createVislibVisController(deps), @@ -90,7 +90,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.heatmap.metricTitle', { defaultMessage: 'Value' }), + title: i18n.translate('visTypeVislib.heatmap.metricTitle', { defaultMessage: 'Value' }), min: 1, max: 1, aggFilter: [ @@ -109,7 +109,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.heatmap.segmentTitle', { + title: i18n.translate('visTypeVislib.heatmap.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -119,7 +119,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('visTypeVislib.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -127,7 +127,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.heatmap.splitTitle', { + title: i18n.translate('visTypeVislib.heatmap.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts index f92875a62cfd7d..490b557b7bafb9 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts @@ -42,11 +42,11 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'histogram', - title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { + title: i18n.translate('visTypeVislib.histogram.histogramTitle', { defaultMessage: 'Vertical Bar', }), icon: 'visBarVertical', - description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', { + description: i18n.translate('visTypeVislib.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), visualization: createVislibVisController(deps), @@ -139,7 +139,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.histogram.metricTitle', { + title: i18n.translate('visTypeVislib.histogram.metricTitle', { defaultMessage: 'Y-axis', }), min: 1, @@ -149,7 +149,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.histogram.radiusTitle', { + title: i18n.translate('visTypeVislib.histogram.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -159,7 +159,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.histogram.segmentTitle', { + title: i18n.translate('visTypeVislib.histogram.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -169,7 +169,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.histogram.groupTitle', { + title: i18n.translate('visTypeVislib.histogram.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -179,7 +179,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.histogram.splitTitle', { + title: i18n.translate('visTypeVislib.histogram.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index ada0c6b44ff701..e8d51fe037a63e 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -42,11 +42,11 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'horizontal_bar', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar', }), icon: 'visBarHorizontal', - description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', { + description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), visualization: createVislibVisController(deps), @@ -138,7 +138,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.metricTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.metricTitle', { defaultMessage: 'Y-axis', }), min: 1, @@ -148,7 +148,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.radiusTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -158,7 +158,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.segmentTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -168,7 +168,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.groupTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -178,7 +178,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.splitTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/index.ts index 3b4bcb6bc3a7e2..80c078de1a10b4 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -24,4 +24,18 @@ export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } +export { + BasicOptions, + RangeOption, + ColorRanges, + SelectOption, + SetColorSchemaOptionsValue, + ColorSchemaOptions, + NumberInputOption, + SwitchOption, + TextInputOption, +} from './components'; + export { ColorModes } from './utils/collections'; + +export * from './types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts index 3d4cf55adc5e0c..775cf63b96f68a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -21,16 +21,6 @@ import { npSetup, npStart } from 'ui/new_platform'; import { PluginInitializerContext } from 'kibana/public'; /* eslint-disable prettier/prettier */ -import { - initializeHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, - // @ts-ignore -} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { - initializePointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, - // @ts-ignore -} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; import { vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, @@ -53,10 +43,6 @@ const setupPlugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: visualizationsSetup, __LEGACY: { - initializeHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, - initializePointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, vislibColor, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index 2970942f221e80..6b10fb0d412234 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -23,13 +23,10 @@ export { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ export { ColorSchema, ColorSchemas, colorSchemas, getHeatmapColors } from 'ui/color_maps'; export { AggConfig, Vis, VisParams } from 'ui/vis'; export { AggType } from 'ui/agg_types'; -export { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from 'ui/vis/vis_types/vislib_vis_legend'; -// @ts-ignore -export { Tooltip } from 'ui/vis/components/tooltip'; // @ts-ignore export { SimpleEmitter } from 'ui/utils/simple_emitter'; // @ts-ignore export { Binder } from 'ui/binder'; -export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { getFormat, getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities'; // @ts-ignore export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/line.ts b/src/legacy/core_plugins/vis_type_vislib/public/line.ts index 35a059fadddcb9..d3f2bc1bb684ca 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/line.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -42,9 +42,9 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'line', - title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), + title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', - description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { + description: i18n.translate('visTypeVislib.line.lineDescription', { defaultMessage: 'Emphasize trends', }), visualization: createVislibVisController(deps), @@ -136,7 +136,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.line.metricTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('visTypeVislib.line.metricTitle', { defaultMessage: 'Y-axis' }), min: 1, aggFilter: ['!geo_centroid', '!geo_bounds'], defaults: [{ schema: 'metric', type: 'count' }], @@ -144,7 +144,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.line.radiusTitle', { defaultMessage: 'Dot size' }), + title: i18n.translate('visTypeVislib.line.radiusTitle', { defaultMessage: 'Dot size' }), min: 0, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], @@ -152,7 +152,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.line.segmentTitle', { defaultMessage: 'X-axis' }), + title: i18n.translate('visTypeVislib.line.segmentTitle', { defaultMessage: 'X-axis' }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -160,7 +160,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.line.groupTitle', { + title: i18n.translate('visTypeVislib.line.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -170,7 +170,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.line.splitTitle', { + title: i18n.translate('visTypeVislib.line.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts index 32307b7a117a1c..3c30030ca45f5b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts @@ -40,9 +40,9 @@ export interface PieVisParams extends CommonVislibParams { export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'pie', - title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), + title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', - description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { + description: i18n.translate('visTypeVislib.pie.pieDescription', { defaultMessage: 'Compare parts of a whole', }), visualization: createVislibVisController(deps), @@ -70,7 +70,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.pie.metricTitle', { + title: i18n.translate('visTypeVislib.pie.metricTitle', { defaultMessage: 'Slice size', }), min: 1, @@ -81,7 +81,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.pie.segmentTitle', { + title: i18n.translate('visTypeVislib.pie.segmentTitle', { defaultMessage: 'Split slices', }), min: 0, @@ -91,7 +91,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.pie.splitTitle', { + title: i18n.translate('visTypeVislib.pie.splitTitle', { defaultMessage: 'Split chart', }), mustBeFirst: true, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts index 4b536caedb1212..af9842fa94fdab 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts @@ -53,7 +53,7 @@ export const createPieVisFn = (deps: KbnVislibVisTypesDependencies) => (): Expre context: { types: ['kibana_datatable'], }, - help: i18n.translate('kbnVislibVisTypes.functions.pie.help', { + help: i18n.translate('visTypeVislib.functions.pie.help', { defaultMessage: 'Pie visualization', }), args: { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index a2e8512b2201b2..0ab2b2120382b1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -46,10 +46,6 @@ type ResponseHandlerProvider = () => { type KbnVislibVisTypesCoreSetup = CoreSetup; export interface LegacyDependencies { - initializeHierarchicalTooltipFormatter: () => Promise; - getHierarchicalTooltipFormatter: () => Promise; - initializePointSeriesTooltipFormatter: () => void; - getPointSeriesTooltipFormatter: () => void; vislibSeriesResponseHandlerProvider: ResponseHandlerProvider; vislibSlicesResponseHandlerProvider: ResponseHandlerProvider; vislibColor: (colors: Array, mappings: any) => (value: any) => any; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts index 810ddeea738341..d8f9fbf7d756ae 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts @@ -31,25 +31,25 @@ export type Positions = $Values; const getPositions = () => [ { - text: i18n.translate('kbnVislibVisTypes.legendPositions.topText', { + text: i18n.translate('visTypeVislib.legendPositions.topText', { defaultMessage: 'Top', }), value: Positions.TOP, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.leftText', { + text: i18n.translate('visTypeVislib.legendPositions.leftText', { defaultMessage: 'Left', }), value: Positions.LEFT, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.rightText', { + text: i18n.translate('visTypeVislib.legendPositions.rightText', { defaultMessage: 'Right', }), value: Positions.RIGHT, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.bottomText', { + text: i18n.translate('visTypeVislib.legendPositions.bottomText', { defaultMessage: 'Bottom', }), value: Positions.BOTTOM, @@ -65,19 +65,19 @@ export type ChartTypes = $Values; const getChartTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.chartTypes.lineText', { + text: i18n.translate('visTypeVislib.chartTypes.lineText', { defaultMessage: 'Line', }), value: ChartTypes.LINE, }, { - text: i18n.translate('kbnVislibVisTypes.chartTypes.areaText', { + text: i18n.translate('visTypeVislib.chartTypes.areaText', { defaultMessage: 'Area', }), value: ChartTypes.AREA, }, { - text: i18n.translate('kbnVislibVisTypes.chartTypes.barText', { + text: i18n.translate('visTypeVislib.chartTypes.barText', { defaultMessage: 'Bar', }), value: ChartTypes.HISTOGRAM, @@ -92,13 +92,13 @@ export type ChartModes = $Values; const getChartModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.chartModes.normalText', { + text: i18n.translate('visTypeVislib.chartModes.normalText', { defaultMessage: 'Normal', }), value: ChartModes.NORMAL, }, { - text: i18n.translate('kbnVislibVisTypes.chartModes.stackedText', { + text: i18n.translate('visTypeVislib.chartModes.stackedText', { defaultMessage: 'Stacked', }), value: ChartModes.STACKED, @@ -114,19 +114,19 @@ export type InterpolationModes = $Values; const getInterpolationModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.straightText', { + text: i18n.translate('visTypeVislib.interpolationModes.straightText', { defaultMessage: 'Straight', }), value: InterpolationModes.LINEAR, }, { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.smoothedText', { + text: i18n.translate('visTypeVislib.interpolationModes.smoothedText', { defaultMessage: 'Smoothed', }), value: InterpolationModes.CARDINAL, }, { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.steppedText', { + text: i18n.translate('visTypeVislib.interpolationModes.steppedText', { defaultMessage: 'Stepped', }), value: InterpolationModes.STEP_AFTER, @@ -148,19 +148,19 @@ export type ScaleTypes = $Values; const getScaleTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.linearText', { + text: i18n.translate('visTypeVislib.scaleTypes.linearText', { defaultMessage: 'Linear', }), value: ScaleTypes.LINEAR, }, { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.logText', { + text: i18n.translate('visTypeVislib.scaleTypes.logText', { defaultMessage: 'Log', }), value: ScaleTypes.LOG, }, { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.squareRootText', { + text: i18n.translate('visTypeVislib.scaleTypes.squareRootText', { defaultMessage: 'Square root', }), value: ScaleTypes.SQUARE_ROOT, @@ -177,25 +177,25 @@ export type AxisModes = $Values; const getAxisModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.axisModes.normalText', { + text: i18n.translate('visTypeVislib.axisModes.normalText', { defaultMessage: 'Normal', }), value: AxisModes.NORMAL, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.percentageText', { + text: i18n.translate('visTypeVislib.axisModes.percentageText', { defaultMessage: 'Percentage', }), value: AxisModes.PERCENTAGE, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.wiggleText', { + text: i18n.translate('visTypeVislib.axisModes.wiggleText', { defaultMessage: 'Wiggle', }), value: AxisModes.WIGGLE, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.silhouetteText', { + text: i18n.translate('visTypeVislib.axisModes.silhouetteText', { defaultMessage: 'Silhouette', }), value: AxisModes.SILHOUETTE, @@ -219,19 +219,19 @@ export type ThresholdLineStyles = $Values; const getThresholdLineStyles = () => [ { value: ThresholdLineStyles.FULL, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.fullText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.fullText', { defaultMessage: 'Full', }), }, { value: ThresholdLineStyles.DASHED, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.dashedText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.dashedText', { defaultMessage: 'Dashed', }), }, { value: ThresholdLineStyles.DOT_DASHED, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.dotdashedText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.dotdashedText', { defaultMessage: 'Dot-dashed', }), }, @@ -239,19 +239,19 @@ const getThresholdLineStyles = () => [ const getRotateOptions = () => [ { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.horizontalText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.horizontalText', { defaultMessage: 'Horizontal', }), value: Rotates.HORIZONTAL, }, { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.verticalText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.verticalText', { defaultMessage: 'Vertical', }), value: Rotates.VERTICAL, }, { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.angledText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.angledText', { defaultMessage: 'Angled', }), value: Rotates.ANGLED, @@ -273,13 +273,13 @@ export type ColorModes = $Values; const getGaugeTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.gauge.gaugeTypes.arcText', { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', { defaultMessage: 'Arc', }), value: GaugeTypes.ARC, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.gaugeTypes.circleText', { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', { defaultMessage: 'Circle', }), value: GaugeTypes.CIRCLE, @@ -295,19 +295,19 @@ export type Alignments = $Values; const getAlignments = () => [ { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentAutomaticTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', { defaultMessage: 'Automatic', }), value: Alignments.AUTOMATIC, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentHorizontalTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', { defaultMessage: 'Horizontal', }), value: Alignments.HORIZONTAL, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentVerticalTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', { defaultMessage: 'Vertical', }), value: Alignments.VERTICAL, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx index adb93ca8011b2e..e2f6ba0d8b5620 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx @@ -29,7 +29,7 @@ function getAreaOptionTabs() { return [ { name: 'advanced', - title: i18n.translate('kbnVislibVisTypes.area.tabs.metricsAxesTitle', { + title: i18n.translate('visTypeVislib.area.tabs.metricsAxesTitle', { defaultMessage: 'Metrics & axes', }), editor: (props: VisOptionsProps) => ( @@ -38,7 +38,7 @@ function getAreaOptionTabs() { }, { name: 'options', - title: i18n.translate('kbnVislibVisTypes.area.tabs.panelSettingsTitle', { + title: i18n.translate('visTypeVislib.area.tabs.panelSettingsTitle', { defaultMessage: 'Panel settings', }), editor: (props: VisOptionsProps) => ( @@ -48,7 +48,7 @@ function getAreaOptionTabs() { ]; } -const countLabel = i18n.translate('kbnVislibVisTypes.area.countText', { +const countLabel = i18n.translate('visTypeVislib.area.countText', { defaultMessage: 'Count', }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx index cff9a0a2e85519..ca49f17e7c264d 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx @@ -20,12 +20,13 @@ import $ from 'jquery'; import React, { RefObject } from 'react'; -import { CUSTOM_LEGEND_VIS_TYPES, VisLegend, Vis, VisParams } from './legacy_imports'; +import { Vis, VisParams } from './legacy_imports'; // @ts-ignore import { Vis as Vislib } from './vislib/vis'; import { Positions } from './utils/collections'; import { KbnVislibVisTypesDependencies } from './plugin'; import { mountReactNode } from '../../../../core/public/utils'; +import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; const legendClassName = { top: 'visLib--legend-top', @@ -76,9 +77,6 @@ export const createVislibVisController = (deps: KbnVislibVisTypesDependencies) = return resolve(); } - await deps.initializeHierarchicalTooltipFormatter(); - await deps.initializePointSeriesTooltipFormatter(); - this.vislibVis = new Vislib(this.chartEl, visParams, deps); this.vislibVis.on('brush', this.vis.API.events.brush); this.vislibVis.on('click', this.vis.API.events.filter); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 0a685cd70e0895..8a35fe3a0f6f82 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -52,7 +52,7 @@ export const createKbnVislibVisTypesFn = ( context: { types: ['kibana_datatable'], }, - help: i18n.translate('kbnVislibVisTypes.functions.vislib.help', { + help: i18n.translate('visTypeVislib.functions.vislib.help', { defaultMessage: 'Vislib visualization', }), args: { diff --git a/src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js similarity index 88% rename from src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js index d0cfab0b54f794..a3aabcb90be624 100644 --- a/src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js @@ -20,18 +20,11 @@ import _ from 'lodash'; import $ from 'jquery'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { PointSeriesTooltipFormatterProvider } from '../_pointseries_tooltip_formatter'; -describe('tooltipFormatter', function() { - let tooltipFormatter; +import { pointSeriesTooltipFormatter } from '../../components/tooltip'; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - tooltipFormatter = Private(PointSeriesTooltipFormatterProvider)(); - }) - ); +describe('tooltipFormatter', function() { + const tooltipFormatter = pointSeriesTooltipFormatter(); function cell($row, i) { return $row diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js index 36c5b60abf5c6b..c7ffc843876e37 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js @@ -18,7 +18,6 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import { getHeatmapColors } from '../../../legacy_imports'; @@ -27,8 +26,6 @@ describe('Vislib Heatmap Color Module Test Suite', function() { const nullValue = null; let notAValue; - beforeEach(ngMock.module('kibana')); - it('should throw an error if schema is invalid', function() { expect(function() { getHeatmapColors(4, 'invalid schema'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js index 55d629adaf2459..db99b881a6e380 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import { labels } from '../../components/labels/labels'; import { dataArray } from '../../components/labels/data_array'; import { uniqLabels } from '../../components/labels/uniq_labels'; @@ -161,21 +161,18 @@ const columnsData = { describe('Vislib Labels Module Test Suite', function() { let uniqSeriesLabels; describe('Labels (main)', function() { - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - seriesLabels = labels(seriesData); - rowsLabels = labels(rowsData); - seriesArr = Array.isArray(seriesLabels); - rowsArr = Array.isArray(rowsLabels); - uniqSeriesLabels = _.chain(rowsData.rows) - .pluck('series') - .flattenDeep() - .pluck('label') - .uniq() - .value(); - }) - ); + beforeEach(() => { + seriesLabels = labels(seriesData); + rowsLabels = labels(rowsData); + seriesArr = Array.isArray(seriesLabels); + rowsArr = Array.isArray(rowsLabels); + uniqSeriesLabels = _.chain(rowsData.rows) + .pluck('series') + .flattenDeep() + .pluck('label') + .uniq() + .value(); + }); it('should be a function', function() { expect(typeof labels).to.be('function'); @@ -224,15 +221,12 @@ describe('Vislib Labels Module Test Suite', function() { let testSeries; let testRows; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - seriesLabels = dataArray(seriesData); - rowsLabels = dataArray(rowsData); - testSeries = Array.isArray(seriesLabels); - testRows = Array.isArray(rowsLabels); - }) - ); + beforeEach(() => { + seriesLabels = dataArray(seriesData); + rowsLabels = dataArray(rowsData); + testSeries = Array.isArray(seriesLabels); + testRows = Array.isArray(rowsLabels); + }); it('should throw an error if the input is not an object', function() { expect(function() { @@ -333,15 +327,12 @@ describe('Vislib Labels Module Test Suite', function() { let uniq; let testArr; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - uniq = uniqLabels(arrObj, function(d) { - return d; - }); - testArr = Array.isArray(uniq); - }) - ); + beforeEach(() => { + uniq = uniqLabels(arrObj, function(d) { + return d; + }); + testArr = Array.isArray(uniq); + }); it('should throw an error if input is not an array', function() { expect(function() { @@ -407,15 +398,12 @@ describe('Vislib Labels Module Test Suite', function() { let columnsArr; let rowsArr; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - columnsLabels = getSeries(columnsData); - rowsLabels = getSeries(rowsData); - columnsArr = Array.isArray(columnsLabels); - rowsArr = Array.isArray(rowsLabels); - }) - ); + beforeEach(() => { + columnsLabels = getSeries(columnsData); + rowsLabels = getSeries(rowsData); + columnsArr = Array.isArray(columnsLabels); + rowsArr = Array.isArray(rowsLabels); + }); it('should throw an error if input is not an object', function() { expect(function() { diff --git a/src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js similarity index 99% rename from src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js index 89d547f2377bab..f1c80c99810204 100644 --- a/src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js @@ -21,7 +21,8 @@ import expect from '@kbn/expect'; import $ from 'jquery'; import _ from 'lodash'; import sinon from 'sinon'; -import { positionTooltip } from '../position_tooltip'; + +import { positionTooltip } from '../../components/tooltip/position_tooltip'; describe('Tooltip Positioning', function() { const sandbox = sinon.createSandbox(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js index e30ca17a1fce7a..734c6d003278fd 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js @@ -19,18 +19,15 @@ import _ from 'lodash'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import VislibProvider from '..'; describe('Vislib Index Test Suite', function() { let vislib; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - vislib = Private(VislibProvider); - }) - ); + beforeEach(() => { + vislib = new VislibProvider(); + }); it('should return an object', function() { expect(_.isObject(vislib)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js index 3081c124150765..bc4a4f9925513c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js @@ -19,17 +19,15 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; -import 'ui/persisted_state'; - import expect from '@kbn/expect'; import $ from 'jquery'; import { Axis } from '../../../lib/axis'; import { VisConfig } from '../../../lib/vis_config'; +import { getMockUiState } from '../fixtures/_vis_fixture'; describe('Vislib Axis Class Test Suite', function() { - let persistedState; + let mockUiState; let yAxis; let el; let fixture; @@ -102,38 +100,34 @@ describe('Vislib Axis Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visAxis--x') - .style('height', '40px'); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visAxis--x') + .style('height', '40px'); - fixture = el.append('div').attr('class', 'x-axis-div'); + fixture = el.append('div').attr('class', 'x-axis-div'); - const visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - persistedState, - $('.x-axis-div')[0], - () => undefined - ); - yAxis = new Axis(visConfig, { - type: 'value', - id: 'ValueAxis-1', - }); + const visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + mockUiState, + $('.x-axis-div')[0], + () => undefined + ); + yAxis = new Axis(visConfig, { + type: 'value', + id: 'ValueAxis-1', + }); - seriesData = data.series.map(series => { - return series.values; - }); - }) - ); + seriesData = data.series.map(series => { + return series.values; + }); + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js index cbb294c3b44e4e..fd25335dd2cd4c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js @@ -20,18 +20,15 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { AxisTitle } from '../../lib/axis/axis_title'; import { AxisConfig } from '../../lib/axis/axis_config'; import { VisConfig } from '../../lib/vis_config'; import { Data } from '../../lib/data'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib AxisTitle Class Test Suite', function() { - let PersistedState; let el; let dataObj; let xTitle; @@ -96,56 +93,53 @@ describe('Vislib AxisTitle Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - PersistedState = $injector.get('PersistedState'); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper'); - - el.append('div') - .attr('class', 'visAxis__column--bottom') - .append('div') - .attr('class', 'axis-title y-axis-title') - .style('height', '20px') - .style('width', '20px'); - - el.append('div') - .attr('class', 'visAxis__column--left') - .append('div') - .attr('class', 'axis-title x-axis-title') - .style('height', '20px') - .style('width', '20px'); - - dataObj = new Data(data, new PersistedState(), () => undefined); - visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - new PersistedState(), - el.node(), - () => undefined - ); - const xAxisConfig = new AxisConfig(visConfig, { - position: 'bottom', - title: { - text: dataObj.get('xAxisLabel'), - }, - }); - const yAxisConfig = new AxisConfig(visConfig, { - position: 'left', - title: { - text: dataObj.get('yAxisLabel'), - }, - }); - xTitle = new AxisTitle(xAxisConfig); - yTitle = new AxisTitle(yAxisConfig); - }) - ); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper'); + + el.append('div') + .attr('class', 'visAxis__column--bottom') + .append('div') + .attr('class', 'axis-title y-axis-title') + .style('height', '20px') + .style('width', '20px'); + + el.append('div') + .attr('class', 'visAxis__column--left') + .append('div') + .attr('class', 'axis-title x-axis-title') + .style('height', '20px') + .style('width', '20px'); + + const uiState = getMockUiState(); + uiState.set('vis.colors', []); + dataObj = new Data(data, getMockUiState(), () => undefined); + visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + getMockUiState(), + el.node(), + () => undefined + ); + const xAxisConfig = new AxisConfig(visConfig, { + position: 'bottom', + title: { + text: dataObj.get('xAxisLabel'), + }, + }); + const yAxisConfig = new AxisConfig(visConfig, { + position: 'left', + title: { + text: dataObj.get('yAxisLabel'), + }, + }); + xTitle = new AxisTitle(xAxisConfig); + yTitle = new AxisTitle(yAxisConfig); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js index b2086d0749a419..b65571becd83c6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js @@ -19,14 +19,14 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import { ChartTitle } from '../../lib/chart_title'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib ChartTitle Class Test Suite', function() { - let persistedState; + let mockUiState; let chartTitle; let el; const data = { @@ -88,36 +88,32 @@ describe('Vislib ChartTitle Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .datum(data); - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .datum(data); + el.append('div') + .attr('class', 'chart-title') + .style('height', '20px'); - el.append('div') - .attr('class', 'chart-title') - .style('height', '20px'); - - const visConfig = new VisConfig( - { - type: 'histogram', - title: { - text: 'rows', - }, + const visConfig = new VisConfig( + { + type: 'histogram', + title: { + text: 'rows', }, - data, - persistedState, - el.node(), - () => undefined - ); - chartTitle = new ChartTitle(visConfig); - }) - ); + }, + data, + mockUiState, + el.node(), + () => undefined + ); + chartTitle = new ChartTitle(visConfig); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js index 5811b1d238163a..d4ec6f363a75b0 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { Data } from '../../lib/data'; +import { getMockUiState } from './fixtures/_vis_fixture'; const seriesData = { label: '', @@ -153,14 +152,11 @@ const colsData = { }; describe('Vislib Data Class Test Suite', function() { - let persistedState; + let mockUiState; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - }) - ); + beforeEach(() => { + mockUiState = getMockUiState(); + }); describe('Data Class (main)', function() { it('should be a function', function() { @@ -168,7 +164,7 @@ describe('Vislib Data Class Test Suite', function() { }); it('should return an object', function() { - const rowIn = new Data(rowsData, persistedState, () => undefined); + const rowIn = new Data(rowsData, mockUiState, () => undefined); expect(_.isObject(rowIn)).to.be(true); }); }); @@ -182,7 +178,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(pieData, persistedState, () => undefined); + data = new Data(pieData, mockUiState, () => undefined); }); it('should remove zero values', function() { @@ -196,7 +192,7 @@ describe('Vislib Data Class Test Suite', function() { let serOut; beforeEach(function() { - serIn = new Data(seriesData, persistedState, () => undefined); + serIn = new Data(seriesData, mockUiState, () => undefined); serOut = serIn.flatten(); }); @@ -210,7 +206,7 @@ describe('Vislib Data Class Test Suite', function() { function testLength(inputData) { return function() { - const data = new Data(inputData, persistedState, () => undefined); + const data = new Data(inputData, mockUiState, () => undefined); const len = _.reduce( data.chartData(), function(sum, chart) { @@ -266,7 +262,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(geohashGridData, persistedState, () => undefined); + data = new Data(geohashGridData, mockUiState, () => undefined); }); describe('getVisData', function() { @@ -287,7 +283,7 @@ describe('Vislib Data Class Test Suite', function() { describe('null value check', function() { it('should return false', function() { - const data = new Data(rowsData, persistedState, () => undefined); + const data = new Data(rowsData, mockUiState, () => undefined); expect(data.hasNullValues()).to.be(false); }); @@ -307,7 +303,7 @@ describe('Vislib Data Class Test Suite', function() { ], }); - const data = new Data(nullRowData, persistedState, () => undefined); + const data = new Data(nullRowData, mockUiState, () => undefined); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js index a93db5637c89d9..760c2e80d84284 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js @@ -19,14 +19,12 @@ import _ from 'lodash'; import d3 from 'd3'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import data from './fixtures/mock_data/date_histogram/_series'; -import getFixturesVislibVisFixtureProvider from './fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from './fixtures/_vis_fixture'; import { SimpleEmitter } from '../../../legacy_imports'; describe('Vislib Dispatch Class Test Suite', function() { @@ -44,17 +42,13 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + vis = getVis(); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + }); afterEach(function() { destroyVis(vis); @@ -71,18 +65,14 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('Stock event handlers', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - persistedState = new ($injector.get('PersistedState'))(); - vis = getVis(); - vis.on('brush', _.noop); - vis.render(data, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + mockUiState = getMockUiState(); + vis = getVis(); + vis.on('brush', _.noop); + vis.render(data, mockUiState); + }); afterEach(function() { destroyVis(vis); @@ -187,42 +177,30 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('Custom event handlers', function() { it('should attach whatever gets passed on vis.on() to chart.events', function(done) { - let vis; - let persistedState; - ngMock.module('kibana'); - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('someEvent', _.noop); - vis.render(data, persistedState); - - vis.handler.charts.forEach(function(chart) { - expect(chart.events.listenerCount('someEvent')).to.be(1); - }); + const vis = getVis(); + const mockUiState = getMockUiState(); + vis.on('someEvent', _.noop); + vis.render(data, mockUiState); - destroyVis(vis); - done(); + vis.handler.charts.forEach(function(chart) { + expect(chart.events.listenerCount('someEvent')).to.be(1); }); + + destroyVis(vis); + done(); }); it('can be added after rendering', function() { - let vis; - let persistedState; - ngMock.module('kibana'); - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - vis.on('someEvent', _.noop); - - vis.handler.charts.forEach(function(chart) { - expect(chart.events.listenerCount('someEvent')).to.be(1); - }); + const vis = getVis(); + const mockUiState = getMockUiState(); + vis.render(data, mockUiState); + vis.on('someEvent', _.noop); - destroyVis(vis); + vis.handler.charts.forEach(function(chart) { + expect(chart.events.listenerCount('someEvent')).to.be(1); }); + + destroyVis(vis); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js index 244386c1b51e67..4523e70ccbb4cb 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js @@ -18,18 +18,15 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import { ErrorHandler } from '../../lib/_error_handler'; describe('Vislib ErrorHandler Test Suite', function() { let errorHandler; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - errorHandler = new ErrorHandler(); - }) - ); + beforeEach(() => { + errorHandler = new ErrorHandler(); + }); describe('validateWidthandHeight Method', function() { it('should throw an error when width and/or height is 0', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js index c49ca732f09159..c001a04f4b6e8c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js @@ -22,15 +22,6 @@ import $ from 'jquery'; import { Vis } from '../../../vis'; -// TODO: remove legacy imports when/of converting tests to jest -import { - setHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, -} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { - setPointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, -} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; import { vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, @@ -73,29 +64,33 @@ const getDeps = () => { return { uiSettings, vislibColor, - getHierarchicalTooltipFormatter, - getPointSeriesTooltipFormatter, vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, }; }; -export default function getVislibFixtures(Private) { - setHierarchicalTooltipFormatter(Private); - setPointSeriesTooltipFormatter(Private); +export const getMockUiState = () => { + const map = new Map(); - return function(visLibParams, element) { - return new Vis( - element || $visCanvas.new(), - _.defaults({}, visLibParams || {}, { - addTooltip: true, - addLegend: true, - defaultYExtents: false, - setYExtents: false, - yAxis: {}, - type: 'histogram', - }), - getDeps() - ); - }; + return (() => ({ + get: (...args) => map.get(...args), + set: (...args) => map.set(...args), + setSilent: (...args) => map.set(...args), + on: () => undefined, + }))(); +}; + +export function getVis(visLibParams, element) { + return new Vis( + element || $visCanvas.new(), + _.defaults({}, visLibParams || {}, { + addTooltip: true, + addLegend: true, + defaultYExtents: false, + setYExtents: false, + yAxis: {}, + type: 'histogram', + }), + getDeps() + ); } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js index b309c97d240006..8e25015c101860 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js @@ -17,36 +17,29 @@ * under the License. */ -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import $ from 'jquery'; // Data import series from '../fixtures/mock_data/date_histogram/_series'; import columns from '../fixtures/mock_data/date_histogram/_columns'; import rows from '../fixtures/mock_data/date_histogram/_rows'; import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import 'ui/persisted_state'; -import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../fixtures/_vis_fixture'; + const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; dateHistogramArray.forEach(function(data, i) { describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function() { - let vis; - let persistedState; const events = ['click', 'brush']; + let vis; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(); + vis.render(data, getMockUiState()); + }); afterEach(function() { vis.destroy(); @@ -107,9 +100,7 @@ dateHistogramArray.forEach(function(data, i) { describe('removeAll Method', function() { beforeEach(function() { - ngMock.inject(function() { - vis.handler.removeAll(vis.element); - }); + vis.handler.removeAll(vis.element); }); it('should remove all DOM elements from the el', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js index c8636f34ce6f8f..f72794e27e834a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js @@ -18,18 +18,17 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; +import $ from 'jquery'; // Data import series from '../fixtures/mock_data/date_histogram/_series'; import columns from '../fixtures/mock_data/date_histogram/_columns'; import rows from '../fixtures/mock_data/date_histogram/_rows'; import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; + import { Layout } from '../../../lib/layout/layout'; -import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../fixtures/_vis_fixture'; import { VisConfig } from '../../../lib/vis_config'; const dateHistogramArray = [series, columns, rows, stackedSeries]; @@ -38,23 +37,18 @@ const names = ['series', 'columns', 'rows', 'stackedSeries']; dateHistogramArray.forEach(function(data, i) { describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function() { let vis; - let persistedState; + let mockUiState; let numberOfCharts; let testLayout; - beforeEach(ngMock.module('kibana')); - - beforeEach(function() { - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - numberOfCharts = vis.handler.charts.length; - }); + beforeEach(() => { + vis = getVis(); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + numberOfCharts = vis.handler.charts.length; }); - afterEach(function() { + afterEach(() => { vis.destroy(); }); @@ -81,7 +75,7 @@ dateHistogramArray.forEach(function(data, i) { type: 'histogram', }, data, - persistedState, + mockUiState, vis.element, () => undefined ); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js index 4dc52309c76fde..cc6d33a2d98daf 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js @@ -18,19 +18,16 @@ */ import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { layoutTypes as layoutType } from '../../../lib/layout/layout_types'; describe('Vislib Layout Types Test Suite', function() { let layoutFunc; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - layoutFunc = layoutType.point_series; - }) - ); + beforeEach(() => { + layoutFunc = layoutType.point_series; + }); it('should be an object', function() { expect(_.isObject(layoutType)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js index 2626fd3ace2b0d..3942aa18891b84 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js @@ -18,9 +18,9 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import $ from 'jquery'; + import { chartSplit } from '../../../../../lib/layout/splits/column_chart/chart_split'; import { chartTitleSplit } from '../../../../../lib/layout/splits/column_chart/chart_title_split'; import { xAxisSplit } from '../../../../../lib/layout/splits/column_chart/x_axis_split'; @@ -150,16 +150,13 @@ describe('Vislib Split Function Test Suite', function() { ], }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization') - .datum(data); - }) - ); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization') + .datum(data); + }); afterEach(function() { el.remove(); @@ -168,11 +165,9 @@ describe('Vislib Split Function Test Suite', function() { describe('chart split function', function() { let fixture; - beforeEach( - ngMock.inject(function() { - fixture = d3.select('.visualization').call(chartSplit); - }) - ); + beforeEach(function() { + fixture = d3.select('.visualization').call(chartSplit); + }); afterEach(function() { fixture.remove(); @@ -192,28 +187,26 @@ describe('Vislib Split Function Test Suite', function() { let newEl; let fixture; - beforeEach( - ngMock.inject(function() { - visEl = el.append('div').attr('class', 'visWrapper'); - visEl.append('div').attr('class', 'visAxis__splitTitles--x'); - visEl.append('div').attr('class', 'visAxis__splitTitles--y'); - visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + beforeEach(function() { + visEl = el.append('div').attr('class', 'visWrapper'); + visEl.append('div').attr('class', 'visAxis__splitTitles--x'); + visEl.append('div').attr('class', 'visAxis__splitTitles--y'); + visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - newEl = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .datum({ series: [] }); + newEl = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .datum({ series: [] }); - newEl.append('div').attr('class', 'visAxis__splitTitles--x'); - newEl.append('div').attr('class', 'visAxis__splitTitles--y'); - newEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - newEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + newEl.append('div').attr('class', 'visAxis__splitTitles--x'); + newEl.append('div').attr('class', 'visAxis__splitTitles--y'); + newEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + newEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - fixture = newEl.selectAll(this.childNodes)[0].length; - }) - ); + fixture = newEl.selectAll(this.childNodes)[0].length; + }); afterEach(function() { newEl.remove(); @@ -237,17 +230,15 @@ describe('Vislib Split Function Test Suite', function() { let fixture; let divs; - beforeEach( - ngMock.inject(function() { - fixture = d3 - .select('body') - .append('div') - .attr('class', 'columns') - .datum({ columns: [{}, {}] }); - d3.select('.columns').call(xAxisSplit); - divs = d3.selectAll('.x-axis-div')[0]; - }) - ); + beforeEach(function() { + fixture = d3 + .select('body') + .append('div') + .attr('class', 'columns') + .datum({ columns: [{}, {}] }); + d3.select('.columns').call(xAxisSplit); + divs = d3.selectAll('.x-axis-div')[0]; + }); afterEach(function() { fixture.remove(); @@ -263,19 +254,17 @@ describe('Vislib Split Function Test Suite', function() { let fixture; let divs; - beforeEach( - ngMock.inject(function() { - fixture = d3 - .select('body') - .append('div') - .attr('class', 'rows') - .datum({ rows: [{}, {}] }); + beforeEach(function() { + fixture = d3 + .select('body') + .append('div') + .attr('class', 'rows') + .datum({ rows: [{}, {}] }); - d3.select('.rows').call(yAxisSplit); + d3.select('.rows').call(yAxisSplit); - divs = d3.selectAll('.y-axis-div')[0]; - }) - ); + divs = d3.selectAll('.y-axis-div')[0]; + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js index 0c744e4fa2b573..8978f80f58dde8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js @@ -18,9 +18,9 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import $ from 'jquery'; + import { chartSplit } from '../../../../../lib/layout/splits/gauge_chart/chart_split'; import { chartTitleSplit } from '../../../../../lib/layout/splits/gauge_chart/chart_title_split'; @@ -148,16 +148,13 @@ describe('Vislib Gauge Split Function Test Suite', function() { ], }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization') - .datum(data); - }) - ); + beforeEach(function() { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization') + .datum(data); + }); afterEach(function() { el.remove(); @@ -166,11 +163,9 @@ describe('Vislib Gauge Split Function Test Suite', function() { describe('chart split function', function() { let fixture; - beforeEach( - ngMock.inject(function() { - fixture = d3.select('.visualization').call(chartSplit); - }) - ); + beforeEach(function() { + fixture = d3.select('.visualization').call(chartSplit); + }); afterEach(function() { fixture.remove(); @@ -188,15 +183,13 @@ describe('Vislib Gauge Split Function Test Suite', function() { describe('chart title split function', function() { let visEl; - beforeEach( - ngMock.inject(function() { - visEl = el.append('div').attr('class', 'visWrapper'); - visEl.append('div').attr('class', 'visAxis__splitTitles--x'); - visEl.append('div').attr('class', 'visAxis__splitTitles--y'); - visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - }) - ); + beforeEach(function() { + visEl = el.append('div').attr('class', 'visWrapper'); + visEl.append('div').attr('class', 'visAxis__splitTitles--x'); + visEl.append('div').attr('class', 'visAxis__splitTitles--y'); + visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + }); afterEach(function() { visEl.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js index bdf886033d8d6f..e9c2ff0d2fa078 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js @@ -19,8 +19,8 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { layoutTypes } from '../../../../lib/layout/layout_types'; describe('Vislib Column Layout Test Suite', function() { @@ -85,16 +85,13 @@ describe('Vislib Column Layout Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization'); - columnLayout = layoutTypes.point_series(el, data); - }) - ); + beforeEach(function() { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization'); + columnLayout = layoutTypes.point_series(el, data); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js index ee3f5a476cd9ac..03646d08298dd6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js @@ -17,12 +17,13 @@ * under the License. */ -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { vislibPointSeriesTypes as pointSeriesConfig } from '../../../lib/types/point_series'; + import percentileTestdata from './testdata_linechart_percentile.json'; import percentileTestdataResult from './testdata_linechart_percentile_result.json'; +import { vislibPointSeriesTypes as pointSeriesConfig } from '../../../lib/types/point_series'; + describe('Point Series Config Type Class Test Suite', function() { let parsedConfig; const histogramConfig = { @@ -95,8 +96,6 @@ describe('Point Series Config Type Class Test Suite', function() { }, }; - beforeEach(ngMock.module('kibana')); - describe('histogram chart', function() { beforeEach(function() { parsedConfig = pointSeriesConfig.column(histogramConfig, data); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js index 3f0253b4a46702..7dfd2ded36a667 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js @@ -18,10 +18,10 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; + import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib VisConfig Class Test Suite', function() { let el; @@ -85,27 +85,23 @@ describe('Vislib VisConfig Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - const PersistedState = $injector.get('PersistedState'); - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .node(); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .node(); - visConfig = new VisConfig( - { - type: 'point_series', - }, - data, - new PersistedState(), - el, - () => undefined - ); - }) - ); + visConfig = new VisConfig( + { + type: 'point_series', + }, + data, + getMockUiState(), + el, + () => undefined + ); + }); afterEach(() => { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js index 09fe067537c7f7..d42562a87b825a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js @@ -19,15 +19,15 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; import $ from 'jquery'; + import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib xAxis Class Test Suite', function() { - let persistedState; + let mockUiState; let xAxis; let el; let fixture; @@ -105,34 +105,30 @@ describe('Vislib xAxis Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visAxis--x') - .style('height', '40px'); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visAxis--x') + .style('height', '40px'); - fixture = el.append('div').attr('class', 'x-axis-div'); + fixture = el.append('div').attr('class', 'x-axis-div'); - const visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - persistedState, - $('.x-axis-div')[0], - () => undefined - ); - xAxis = new Axis(visConfig, { - type: 'category', - id: 'CategoryAxis-1', - }); - }) - ); + const visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + mockUiState, + $('.x-axis-div')[0], + () => undefined + ); + xAxis = new Axis(visConfig, { + type: 'category', + id: 'CategoryAxis-1', + }); + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js index e857aca3bf3edc..f73011d6616451 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js @@ -19,15 +19,15 @@ import _ from 'lodash'; import d3 from 'd3'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import 'ui/persisted_state'; import $ from 'jquery'; +import expect from '@kbn/expect'; + import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; const YAxis = Axis; -let persistedState; +let mockUiState; let el; let buildYAxis; let yAxis; @@ -96,7 +96,7 @@ function createData(seriesData) { type: 'histogram', }, data, - persistedState, + mockUiState, node, () => undefined ); @@ -121,15 +121,10 @@ function createData(seriesData) { } describe('Vislib yAxis Class Test Suite', function() { - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - expect($('.y-axis-wrapper')).to.have.length(0); - }) - ); + beforeEach(() => { + mockUiState = getMockUiState(); + expect($('.y-axis-wrapper')).to.have.length(0); + }); afterEach(function() { if (el) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js index a6d1c4daf5d2cf..4852f71d8c45b3 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js @@ -19,19 +19,15 @@ import _ from 'lodash'; import $ from 'jquery'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import series from './lib/fixtures/mock_data/date_histogram/_series'; import columns from './lib/fixtures/mock_data/date_histogram/_columns'; import rows from './lib/fixtures/mock_data/date_histogram/_rows'; import stackedSeries from './lib/fixtures/mock_data/date_histogram/_stacked_series'; -import getFixturesVislibVisFixtureProvider from './lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from './lib/fixtures/_vis_fixture'; const dataArray = [series, columns, rows, stackedSeries]; - const names = ['series', 'columns', 'rows', 'stackedSeries']; dataArray.forEach(function(data, i) { @@ -39,19 +35,15 @@ dataArray.forEach(function(data, i) { const beforeEvent = 'click'; const afterEvent = 'brush'; let vis; - let persistedState; + let mockUiState; let secondVis; let numberOfCharts; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - secondVis = getVis(); - }) - ); + beforeEach(() => { + vis = getVis(); + secondVis = getVis(); + mockUiState = getMockUiState(); + }); afterEach(function() { vis.destroy(); @@ -60,7 +52,7 @@ dataArray.forEach(function(data, i) { describe('render Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); numberOfCharts = vis.handler.charts.length; }); @@ -78,7 +70,7 @@ dataArray.forEach(function(data, i) { it('should throw an error if no data is provided', function() { expect(function() { - vis.render(null, persistedState); + vis.render(null, mockUiState); }).to.throwError(); }); }); @@ -91,8 +83,8 @@ dataArray.forEach(function(data, i) { describe('destroy Method', function() { beforeEach(function() { - vis.render(data, persistedState); - secondVis.render(data, persistedState); + vis.render(data, mockUiState); + secondVis.render(data, mockUiState); secondVis.destroy(); }); @@ -107,7 +99,7 @@ dataArray.forEach(function(data, i) { describe('set Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); vis.set('addLegend', false); vis.set('offset', 'wiggle'); }); @@ -120,7 +112,7 @@ dataArray.forEach(function(data, i) { describe('get Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should get attribute values', function() { @@ -142,7 +134,7 @@ dataArray.forEach(function(data, i) { }); // Render chart - vis.render(data, persistedState); + vis.render(data, mockUiState); // Add event after charts have rendered listeners.forEach(function(listener) { @@ -196,7 +188,7 @@ dataArray.forEach(function(data, i) { vis.off(beforeEvent, listener1); // Render chart - vis.render(data, persistedState); + vis.render(data, mockUiState); // Add event after charts have rendered listeners.forEach(function(listener) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js index 7fe350bd85e052..c3f5859eb454cc 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js @@ -18,14 +18,11 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import _ from 'lodash'; import $ from 'jquery'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; const dataTypesArray = { 'series pos': require('../lib/fixtures/mock_data/date_histogram/_series'), @@ -46,18 +43,14 @@ const visLibParams = { _.forOwn(dataTypesArray, function(dataType, dataTypeName) { describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(dataType, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(dataType, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -97,17 +90,15 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { let d3selectedPath; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - path = $(chart.chartEl).find('path')[0]; - d3selectedPath = d3.select(path)[0][0]; + beforeEach(function() { + vis.handler.charts.forEach(function(chart) { + path = $(chart.chartEl).find('path')[0]; + d3selectedPath = d3.select(path)[0][0]; - // d3 instance of click and hover - onMouseOver = !!d3selectedPath.__onmouseover; - }); - }) - ); + // d3 instance of click and hover + onMouseOver = !!d3selectedPath.__onmouseover; + }); + }); it('should attach a hover event', function() { vis.handler.charts.forEach(function() { @@ -124,20 +115,18 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { let onClick; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - circle = $(chart.chartEl).find('circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }) - ); + beforeEach(() => { + vis.handler.charts.forEach(function(chart) { + circle = $(chart.chartEl).find('circle')[0]; + brush = $(chart.chartEl).find('.brush'); + d3selectedCircle = d3.select(circle)[0][0]; + + // d3 instance of click and hover + onBrush = !!brush; + onClick = !!d3selectedCircle.__onclick; + onMouseOver = !!d3selectedCircle.__onmouseover; + }); + }); // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes @@ -219,7 +208,7 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(dataType, persistedState); + vis.render(dataType, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -238,7 +227,7 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(dataType, persistedState); + vis.render(dataType, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js index 088d3377af4dd5..9653f9abab6fb8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js @@ -18,16 +18,12 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { Chart } from '../../visualizations/_chart'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; describe('Vislib _chart Test Suite', function() { - let persistedState; let vis; let el; let myChart; @@ -109,30 +105,24 @@ describe('Vislib _chart Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'column-chart'); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'column-chart'); - config = { - type: 'histogram', - addTooltip: true, - addLegend: true, - zeroFill: true, - }; + config = { + type: 'histogram', + addTooltip: true, + addLegend: true, + zeroFill: true, + }; - vis = getVis(config, el[0][0]); - vis.render(data, persistedState); + vis = getVis(config, el[0][0]); + vis.render(data, getMockUiState()); - myChart = vis.handler.charts[0]; - }) - ); + myChart = vis.handler.charts[0]; + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js index d02060ef29bdd4..2216294fcbac11 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js @@ -17,12 +17,10 @@ * under the License. */ -import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; - +import $ from 'jquery'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import series from '../lib/fixtures/mock_data/date_histogram/_series'; @@ -31,11 +29,11 @@ import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; import histogramRows from '../lib/fixtures/mock_data/histogram/_rows'; import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; + import { seriesMonthlyInterval } from '../lib/fixtures/mock_data/date_histogram/_series_monthly_interval'; import { rowsSeriesWithHoles } from '../lib/fixtures/mock_data/date_histogram/_rows_series_with_holes'; import rowsWithZeros from '../lib/fixtures/mock_data/date_histogram/_rows'; -import $ from 'jquery'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -54,7 +52,7 @@ dataTypesArray.forEach(function(dataType) { describe('Vislib Column Chart Test Suite for ' + name + ' Data', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -67,16 +65,12 @@ dataTypesArray.forEach(function(dataType) { }, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(data, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(data, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -200,7 +194,7 @@ dataTypesArray.forEach(function(dataType) { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -219,7 +213,7 @@ dataTypesArray.forEach(function(dataType) { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { @@ -247,7 +241,7 @@ dataTypesArray.forEach(function(dataType) { describe('stackData method - data set with zeros in percentage mode', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -256,22 +250,18 @@ describe('stackData method - data set with zeros in percentage mode', function() zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + }); afterEach(function() { vis.destroy(); }); it('should not mutate the injected zeros', function() { - vis.render(seriesMonthlyInterval, persistedState); + vis.render(seriesMonthlyInterval, mockUiState); expect(vis.handler.charts).to.have.length(1); const chart = vis.handler.charts[0]; @@ -284,7 +274,7 @@ describe('stackData method - data set with zeros in percentage mode', function() }); it('should not mutate zeros that exist in the data', function() { - vis.render(rowsWithZeros, persistedState); + vis.render(rowsWithZeros, mockUiState); expect(vis.handler.charts).to.have.length(2); const chart = vis.handler.charts[0]; @@ -298,7 +288,7 @@ describe('stackData method - data set with zeros in percentage mode', function() describe('datumWidth - split chart data set with holes', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -307,16 +297,12 @@ describe('datumWidth - split chart data set with holes', function() { zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(rowsSeriesWithHoles, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(rowsSeriesWithHoles, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -336,7 +322,7 @@ describe('datumWidth - split chart data set with holes', function() { describe('datumWidth - monthly interval', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -345,16 +331,12 @@ describe('datumWidth - monthly interval', function() { zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(seriesMonthlyInterval, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(seriesMonthlyInterval, mockUiState); + }); afterEach(function() { vis.destroy(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js index 074b34e1c03c43..fe25734fcbfde5 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js @@ -17,21 +17,15 @@ * under the License. */ -import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import data from '../lib/fixtures/mock_data/terms/_seriesMultiple'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; describe('Vislib Gauge Chart Test Suite', function() { - let PersistedState; - let vislibVis; let vis; - let persistedState; let chartEl; const visLibParams = { type: 'gauge', @@ -81,21 +75,15 @@ describe('Vislib Gauge Chart Test Suite', function() { vis.destroy(); $('.visChart').remove(); } - vis = vislibVis(config); - persistedState = new PersistedState(); + vis = getVis(config); vis.on('brush', _.noop); - vis.render(data, persistedState); + vis.render(data, getMockUiState()); chartEl = vis.handler.charts[0].chartEl; } - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - vislibVis = getFixturesVislibVisFixtureProvider(Private); - PersistedState = $injector.get('PersistedState'); - generateVis(); - }) - ); + beforeEach(() => { + generateVis(); + }); afterEach(function() { vis.destroy(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js index bf1dbad0b44cf1..f4c952be191deb 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js @@ -17,12 +17,10 @@ * under the License. */ -import ngMock from 'ng_mock'; import _ from 'lodash'; +import $ from 'jquery'; import d3 from 'd3'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import series from '../lib/fixtures/mock_data/date_histogram/_series'; @@ -30,8 +28,8 @@ import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_n import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -48,10 +46,8 @@ describe('Vislib Heatmap Chart Test Suite', function() { const data = dataType[1]; describe('for ' + name + ' Data', function() { - let PersistedState; - let vislibVis; let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'heatmap', addLegend: true, @@ -66,20 +62,15 @@ describe('Vislib Heatmap Chart Test Suite', function() { function generateVis(opts = {}) { const config = _.defaultsDeep({}, opts, visLibParams); - vis = vislibVis(config); - persistedState = new PersistedState(); + vis = getVis(config); + mockUiState = getMockUiState(); vis.on('brush', _.noop); - vis.render(data, persistedState); + vis.render(data, mockUiState); } - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - vislibVis = getFixturesVislibVisFixtureProvider(Private); - PersistedState = $injector.get('PersistedState'); - generateVis(); - }) - ); + beforeEach(() => { + generateVis(); + }); afterEach(function() { vis.destroy(); @@ -174,7 +165,7 @@ describe('Vislib Heatmap Chart Test Suite', function() { }); it('should define default colors', function() { - expect(persistedState.get('vis.defaultColors')).to.not.be(undefined); + expect(mockUiState.get('vis.defaultColors')).to.not.be(undefined); }); it('should set custom range', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js index d010944a19e47d..1269fe7bcf62e9 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js @@ -19,12 +19,9 @@ import d3 from 'd3'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; -import 'ui/persisted_state'; - // Data import seriesPos from '../lib/fixtures/mock_data/date_histogram/_series'; import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; @@ -32,7 +29,8 @@ import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import histogramColumns from '../lib/fixtures/mock_data/histogram/_columns'; import rangeRows from '../lib/fixtures/mock_data/range/_rows'; import termSeries from '../lib/fixtures/mock_data/terms/_series'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; const dataTypes = [ ['series pos', seriesPos], @@ -50,25 +48,21 @@ describe('Vislib Line Chart', function() { describe(name + ' Data', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - const visLibParams = { - type: 'line', - addLegend: true, - addTooltip: true, - drawLinesBetweenPoints: true, - }; - - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - vis.on('brush', _.noop); - }) - ); + let mockUiState; + + beforeEach(() => { + const visLibParams = { + type: 'line', + addLegend: true, + addTooltip: true, + drawLinesBetweenPoints: true, + }; + + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + vis.on('brush', _.noop); + }); afterEach(function() { vis.destroy(); @@ -82,20 +76,18 @@ describe('Vislib Line Chart', function() { let onClick; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - circle = $(chart.chartEl).find('.circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }) - ); + beforeEach(function() { + vis.handler.charts.forEach(function(chart) { + circle = $(chart.chartEl).find('.circle')[0]; + brush = $(chart.chartEl).find('.brush'); + d3selectedCircle = d3.select(circle)[0][0]; + + // d3 instance of click and hover + onBrush = !!brush; + onClick = !!d3selectedCircle.__onclick; + onMouseOver = !!d3selectedCircle.__onmouseover; + }); + }); // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes @@ -177,7 +169,7 @@ describe('Vislib Line Chart', function() { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -196,7 +188,7 @@ describe('Vislib Line Chart', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 381dfcd387cc20..f38fa47393a143 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -18,19 +18,16 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import _ from 'lodash'; import $ from 'jquery'; - import expect from '@kbn/expect'; -// TODO: Remove ui imports once converting to jest -import 'ui/persisted_state'; + import { vislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; import fixtures from 'fixtures/fake_hierarchical_data'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; import { Vis, tabifyAggResponse } from '../../../legacy_imports'; const rowAgg = [ @@ -123,35 +120,31 @@ describe('No global chart settings', function() { addTooltip: true, }; let chart1; - let persistedState; + let mockUiState; let indexPattern; let responseHandler; let data1; let stubVis1; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - chart1 = getVis(visLibParams1); - persistedState = new ($injector.get('PersistedState'))(); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - responseHandler = vislibSlicesResponseHandlerProvider().handler; - - let id1 = 1; - stubVis1 = new Vis(indexPattern, { - type: 'pie', - aggs: rowAgg, - }); + beforeEach(() => { + chart1 = getVis(visLibParams1); + mockUiState = getMockUiState(); + indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); + responseHandler = vislibSlicesResponseHandlerProvider().handler; - stubVis1.isHierarchical = () => true; + let id1 = 1; + stubVis1 = new Vis(indexPattern, { + type: 'pie', + aggs: rowAgg, + }); - // We need to set the aggs to a known value. - _.each(stubVis1.aggs.aggs, function(agg) { - agg.id = 'agg_' + id1++; - }); - }) - ); + stubVis1.isHierarchical = () => true; + + // We need to set the aggs to a known value. + _.each(stubVis1.aggs.aggs, function(agg) { + agg.id = 'agg_' + id1++; + }); + }); beforeEach(async () => { const table1 = tabifyAggResponse(stubVis1.aggs, fixtures.threeTermBuckets, { @@ -159,7 +152,7 @@ describe('No global chart settings', function() { }); data1 = await responseHandler(table1, rowAggDimensions); - chart1.render(data1, persistedState); + chart1.render(data1, mockUiState); }); afterEach(function() { @@ -216,40 +209,37 @@ describe('Vislib PieChart Class Test Suite', function() { addTooltip: true, }; let vis; - let persistedState; + let mockUiState; let indexPattern; let data; let stubVis; let responseHandler; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - responseHandler = vislibSlicesResponseHandlerProvider().handler; - - let id = 1; - stubVis = new Vis(indexPattern, { - type: 'pie', - aggs: dataAgg, - }); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); + responseHandler = vislibSlicesResponseHandlerProvider().handler; - // We need to set the aggs to a known value. - _.each(stubVis.aggs.aggs, function(agg) { - agg.id = 'agg_' + id++; - }); - }) - ); + let id = 1; + stubVis = new Vis(indexPattern, { + type: 'pie', + aggs: dataAgg, + }); + + // We need to set the aggs to a known value. + _.each(stubVis.aggs.aggs, function(agg) { + agg.id = 'agg_' + id++; + }); + }); beforeEach(async () => { const table = tabifyAggResponse(stubVis.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true, }); data = await responseHandler(table, dataDimensions); - vis.render(data, persistedState); + + vis.render(data, mockUiState); }); afterEach(function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js index ec22d43c08cb2b..d69f952325ed02 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js @@ -18,11 +18,11 @@ */ import d3 from 'd3'; +import $ from 'jquery'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import series from '../lib/fixtures/mock_data/date_histogram/_series'; import terms from '../lib/fixtures/mock_data/terms/_columns'; -import $ from 'jquery'; import { TimeMarker } from '../../visualizations/time_marker'; describe('Vislib Time Marker Test Suite', function() { @@ -57,26 +57,23 @@ describe('Vislib Time Marker Test Suite', function() { let maxDomain; let domain; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - minDomain = getExtent(series.series, d3.min); - maxDomain = getExtent(series.series, d3.max); - domain = [minDomain, maxDomain]; - xScale = d3.time - .scale() - .domain(domain) - .range([0, 500]); - defaultMarker = new TimeMarker(times, xScale, height); - customMarker = new TimeMarker(myTimes, xScale, height); - - selection = d3 - .select('body') - .append('div') - .attr('class', 'marker'); - selection.datum(series); - }) - ); + beforeEach(function() { + minDomain = getExtent(series.series, d3.min); + maxDomain = getExtent(series.series, d3.max); + domain = [minDomain, maxDomain]; + xScale = d3.time + .scale() + .domain(domain) + .range([0, 500]); + defaultMarker = new TimeMarker(times, xScale, height); + customMarker = new TimeMarker(myTimes, xScale, height); + + selection = d3 + .select('body') + .append('div') + .attr('class', 'marker'); + selection.datum(series); + }); afterEach(function() { selection.remove('*'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js index e150ca58c15cfd..c8f0faf8dcca5d 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js @@ -17,20 +17,17 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import _ from 'lodash'; +import expect from '@kbn/expect'; + import { visTypes } from '../../visualizations/vis_types'; describe('Vislib Vis Types Test Suite', function() { let visFunc; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - visFunc = visTypes.point_series; - }) - ); + beforeEach(function() { + visFunc = visTypes.point_series; + }); it('should be an object', function() { expect(_.isObject(visTypes)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss index 0344fbb5359ece..78e16224a67a3c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss @@ -1,6 +1,9 @@ @import './variables'; +@import './vislib_vis_type'; @import './lib/index'; +@import './components/tooltip/index'; +@import './components/legend/index'; @import './visualizations/point_series/index'; @import './visualizations/gauges/index'; diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss similarity index 99% rename from src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss index c1f369d3e3536f..c03aa19140de06 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss @@ -7,12 +7,15 @@ &.visLib--legend-left { flex-direction: row-reverse; } + &.visLib--legend-right { flex-direction: row; } + &.visLib--legend-top { flex-direction: column-reverse; } + &.visLib--legend-bottom { flex-direction: column; } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/__snapshots__/legend.test.tsx.snap similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/__snapshots__/legend.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss new file mode 100644 index 00000000000000..53617a984dcf51 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss @@ -0,0 +1 @@ +@import './_legend'; diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss similarity index 85% rename from src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss index 62050ce4e99fd3..b1a59f88a348ad 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss @@ -1,4 +1,4 @@ -@import '../../../../core_plugins/vis_type_vislib/public/vislib/variables'; +@import '../../variables'; // NOTE: Some of the styles attempt to align with the TSVB legend @@ -14,17 +14,16 @@ $visLegendLineHeight: $euiSize; display: flex; padding: $euiSizeXS; background-color: $euiColorEmptyShade; - transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; + transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; &:focus { box-shadow: none; - background-color: $euiFocusBackgroundColor !important; + background-color: $euiFocusBackgroundColor !important; // sass-lint:disable-line no-important } } .visLegend__toggle--isOpen { - background-color: transparentize($euiColorDarkestShade, 0.9); + background-color: transparentize($euiColorDarkestShade, .9); opacity: 1; } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts similarity index 94% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts index ebf132f0ab697f..230cda4c35ff8d 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { VisLegend } from './vislib_vis_legend'; +export { VisLegend } from './legend'; export { CUSTOM_LEGEND_VIS_TYPES } from './models'; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx similarity index 96% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index 839dc0024bbea5..6f0a5a3784b070 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -23,7 +23,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiButtonGroup } from '@elastic/eui'; -import { VisLegend, VisLegendProps } from '../vislib_vis_legend/vislib_vis_legend'; +import { VisLegend, VisLegendProps } from './legend'; import { legendColors } from './models'; jest.mock('@elastic/eui', () => ({ @@ -31,10 +31,10 @@ jest.mock('@elastic/eui', () => ({ htmlIdGenerator: jest.fn().mockReturnValue(() => 'legendId'), })); -jest.mock('../../../visualize/loader/pipeline_helpers/utilities', () => ({ +jest.mock('../../../legacy_imports', () => ({ getTableAggs: jest.fn(), })); -jest.mock('../../../../../core_plugins/visualizations/public', () => ({ +jest.mock('../../../../../visualizations/public', () => ({ createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), })); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx similarity index 93% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index d98590f9885b9d..0eec557dd334ee 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -24,11 +24,11 @@ import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; // @ts-ignore -import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; +import { createFiltersFromEvent } from '../../../../../visualizations/public'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; -import { VisLegendItem } from './vislib_vis_legend_item'; +import { VisLegendItem } from './legend_item'; import { getPieNames } from './pie_utils'; -import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; +import { getTableAggs } from '../../../legacy_imports'; export interface VisLegendProps { vis: any; @@ -138,7 +138,7 @@ export class VisLegend extends PureComponent { this.setState({ labels: [ { - label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.loadingLabel', { defaultMessage: 'loading…', }), }, @@ -244,13 +244,13 @@ export class VisLegend extends PureComponent { className={classNames('visLegend__toggle kbn-resetFocusState', { 'visLegend__toggle--isOpen': open, })} - aria-label={i18n.translate('common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel', { + aria-label={i18n.translate('visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel', { defaultMessage: 'Toggle legend', })} aria-expanded={Boolean(open)} aria-controls={this.legendId} data-test-subj="vislibToggleLegend" - title={i18n.translate('common.ui.vis.visTypes.legend.toggleLegendButtonTitle', { + title={i18n.translate('visTypeVislib.vislib.legend.toggleLegendButtonTitle', { defaultMessage: 'Toggle legend', })} > diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx similarity index 92% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx index 7376fabfe738be..09c8a838532bf6 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx @@ -77,7 +77,7 @@ const VisLegendItemComponent = ({ const filterOptions: EuiButtonGroupOption[] = [ { id: 'filterIn', - label: i18n.translate('common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.filterForValueButtonAriaLabel', { defaultMessage: 'Filter for value {legendDataLabel}', values: { legendDataLabel: item.label }, }), @@ -86,7 +86,7 @@ const VisLegendItemComponent = ({ }, { id: 'filterOut', - label: i18n.translate('common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel', { defaultMessage: 'Filter out value {legendDataLabel}', values: { legendDataLabel: item.label }, }), @@ -105,7 +105,7 @@ const VisLegendItemComponent = ({ type="multi" isIconOnly isFullWidth - legend={i18n.translate('common.ui.vis.visTypes.legend.filterOptionsLegend', { + legend={i18n.translate('visTypeVislib.vislib.legend.filterOptionsLegend', { defaultMessage: '{legendDataLabel}, filter options', values: { legendDataLabel: item.label }, })} @@ -131,7 +131,7 @@ const VisLegendItemComponent = ({ onBlur={onUnhighlight} data-label={item.label} title={item.label} - aria-label={i18n.translate('common.ui.vis.visTypes.legend.toggleOptionsButtonAriaLabel', { + aria-label={i18n.translate('visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel', { defaultMessage: '{legendDataLabel}, toggle options', values: { legendDataLabel: item.label }, })} @@ -163,7 +163,7 @@ const VisLegendItemComponent = ({
diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/models.ts similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/models.ts diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts diff --git a/src/legacy/ui/public/vis/components/tooltip/_collect_branch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_collect_branch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.js diff --git a/src/legacy/ui/public/vis/components/tooltip/_collect_branch.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.test.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_collect_branch.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.test.js diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js new file mode 100644 index 00000000000000..22fff9cf6a317f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import _ from 'lodash'; +import numeral from 'numeral'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; + +import { collectBranch } from './_collect_branch'; + +export function hierarchicalTooltipFormatter(metricFieldFormatter) { + return function({ datum }) { + // Collect the current leaf and parents into an array of values + const rows = collectBranch(datum); + + // Map those values to what the tooltipSource.rows format. + _.forEachRight(rows, function(row) { + row.spacer = _.escape(_.repeat(' ', row.depth)); + + let percent; + if (row.item.percentOfGroup !== null && row.item.percentOfGroup !== undefined) { + percent = row.item.percentOfGroup; + } + + row.metric = metricFieldFormatter ? metricFieldFormatter.convert(row.metric) : row.metric; + + if (percent !== null && percent !== undefined) { + row.metric += ' (' + numeral(percent).format('0.[00]%') + ')'; + } + + return row; + }); + + return renderToStaticMarkup( + + + + + + + + + + + + {rows.map((row, index) => ( + + + + + + ))} + +
{/* {metricCol.label} */}
+
+ + {row.field} +
+
+
{row.bucket}
+
{row.metric}
+
+ ); + }; +} diff --git a/src/legacy/ui/public/vis/components/tooltip/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_index.scss similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_index.scss diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js new file mode 100644 index 00000000000000..13c9b8024aac3d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +export function pointSeriesTooltipFormatter() { + return function tooltipFormatter({ datum, data }) { + if (!datum) return ''; + + const details = []; + + const currentSeries = data.series && data.series.find(serie => serie.rawId === datum.seriesId); + const addDetail = (label, value) => details.push({ label, value }); + + if (datum.extraMetrics) { + datum.extraMetrics.forEach(metric => { + addDetail(metric.label, metric.value); + }); + } + + if (datum.x !== null && datum.x !== undefined) { + addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x)); + } + + if (datum.y !== null && datum.y !== undefined) { + const value = datum.yScale ? datum.yScale * datum.y : datum.y; + addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); + } + + if (datum.z !== null && datum.z !== undefined) { + addDetail(currentSeries.zLabel, currentSeries.zAxisFormatter(datum.z)); + } + if (datum.series && datum.parent) { + const dimension = datum.parent; + addDetail(dimension.title, datum.series); + } + if (datum.tableRaw) { + addDetail(datum.tableRaw.title, datum.tableRaw.value); + } + + return renderToStaticMarkup( + + + {details.map((detail, index) => ( + + + + + + ))} + +
+
{detail.label}
+
+
+ {detail.value} + {detail.percent && ({detail.percent})} +
+
+ ); + }; +} diff --git a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss similarity index 97% rename from src/legacy/ui/public/vis/components/tooltip/_tooltip.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss index 5a30ded7c2e5bd..bafec7edf3b943 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss @@ -28,7 +28,7 @@ } .visTooltip__header { - margin: 0 0 $euiSizeS 0; + margin: 0 0 $euiSizeS; padding: $euiSizeXS $euiSizeS; display: flex; align-items: center; @@ -41,6 +41,7 @@ margin-top: $euiSizeS; } } + .visTooltip__labelContainer, .visTooltip__valueContainer { overflow-wrap: break-word; diff --git a/src/legacy/ui/public/vis/components/tooltip/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js similarity index 80% rename from src/legacy/ui/public/vis/components/tooltip/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js index e596a8af865331..e394981125a1f5 100644 --- a/src/legacy/ui/public/vis/components/tooltip/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js @@ -17,4 +17,6 @@ * under the License. */ -export { Tooltip, TooltipProvider } from './tooltip'; +export { Tooltip } from './tooltip'; +export { hierarchicalTooltipFormatter } from './_hierarchical_tooltip_formatter'; +export { pointSeriesTooltipFormatter } from './_pointseries_tooltip_formatter'; diff --git a/src/legacy/ui/public/vis/components/tooltip/position_tooltip.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/position_tooltip.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js diff --git a/src/legacy/ui/public/vis/components/tooltip/tooltip.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js similarity index 97% rename from src/legacy/ui/public/vis/components/tooltip/tooltip.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js index 764ab24da67402..f7d29164eec6f1 100644 --- a/src/legacy/ui/public/vis/components/tooltip/tooltip.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js @@ -19,9 +19,10 @@ import d3 from 'd3'; import _ from 'lodash'; -import { Binder } from '../../../binder'; -import { positionTooltip } from './position_tooltip'; import $ from 'jquery'; + +import { Binder } from '../../../legacy_imports'; +import { positionTooltip } from './position_tooltip'; import theme from '@elastic/eui/dist/eui_theme_light.json'; let allContents = []; @@ -217,7 +218,7 @@ Tooltip.prototype.render = function() { return content.id !== id; }); - if (html) allContents.push({ id: id, html: html, order: order }); + if (html) allContents.push({ id, html, order }); const allHtml = _(allContents) .sortBy('order') @@ -253,7 +254,3 @@ Tooltip.prototype.destroy = function() { this.hide(); this.binder.destroy(); }; - -export function TooltipProvider() { - return Tooltip; -} diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js index 1c84f98614b05b..3caaf99cbb5c12 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js @@ -21,7 +21,7 @@ import d3 from 'd3'; import _ from 'lodash'; import { ErrorHandler } from './_error_handler'; -import { Tooltip } from '../../legacy_imports'; +import { Tooltip } from '../components/tooltip'; export class ChartTitle extends ErrorHandler { constructor(visConfig) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js index eab3bc02f4eec9..ebaf64874d7298 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js @@ -229,7 +229,7 @@ export const vislibPointSeriesTypes = { const tooManySeries = defaults.charts.length && defaults.charts[0].series.length > cfg.heatmapMaxBuckets; if (hasCharts && tooManySeries) { - defaults.error = i18n.translate('kbnVislibVisTypes.vislib.heatmap.maxBucketsText', { + defaults.error = i18n.translate('visTypeVislib.vislib.heatmap.maxBucketsText', { defaultMessage: 'There are too many series defined ({nr}). The configured maximum is {max}.', values: { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js index ac6e8130a846ad..a36c7c4774dadf 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js @@ -22,7 +22,12 @@ import _ from 'lodash'; import { dataLabel } from '../lib/_data_label'; import { Dispatch } from '../lib/dispatch'; -import { Tooltip, getFormat } from '../../legacy_imports'; +import { getFormat } from '../../legacy_imports'; +import { + Tooltip, + hierarchicalTooltipFormatter, + pointSeriesTooltipFormatter, +} from '../components/tooltip'; /** * The Base Class for all visualizations. @@ -45,8 +50,8 @@ export class Chart { const fieldFormatter = getFormat(this.handler.data.get('tooltipFormatter')); const tooltipFormatterProvider = this.handler.visConfig.get('type') === 'pie' - ? deps.getHierarchicalTooltipFormatter() - : deps.getPointSeriesTooltipFormatter(); + ? hierarchicalTooltipFormatter + : pointSeriesTooltipFormatter; const tooltipFormatter = tooltipFormatterProvider(fieldFormatter); if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index c838c51d34bf5a..53f06c79d178c2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -21,7 +21,7 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { Tooltip } from '../../legacy_imports'; +import { Tooltip } from '../components/tooltip'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; import { seriesTypes } from './point_series/series_types'; @@ -153,18 +153,10 @@ export class PointSeries extends Chart { .attr('class', 'endzone') .append('rect') .attr('class', 'zone') - .attr('x', function(d) { - return isHorizontal ? d.x : 0; - }) - .attr('y', function(d) { - return isHorizontal ? 0 : d.x; - }) - .attr('height', function(d) { - return isHorizontal ? height : d.w; - }) - .attr('width', function(d) { - return isHorizontal ? d.w : width; - }); + .attr('x', d => (isHorizontal ? d.x : 0)) + .attr('y', d => (isHorizontal ? 0 : d.x)) + .attr('height', d => (isHorizontal ? height : d.w)) + .attr('width', d => (isHorizontal ? d.w : width)); function callPlay(event) { const boundData = event.target.__data__; diff --git a/src/legacy/ui/public/vis/_index.scss b/src/legacy/ui/public/vis/_index.scss index 4fb07557977d31..36d586abdb147b 100644 --- a/src/legacy/ui/public/vis/_index.scss +++ b/src/legacy/ui/public/vis/_index.scss @@ -1,4 +1,2 @@ -@import './components/index'; @import './editors/index'; @import './map/index'; -@import './vis_types/index'; diff --git a/src/legacy/ui/public/vis/components/_index.scss b/src/legacy/ui/public/vis/components/_index.scss deleted file mode 100644 index 0d79aa9c458aca..00000000000000 --- a/src/legacy/ui/public/vis/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './tooltip/index'; diff --git a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html deleted file mode 100644 index 43c4793384bf13..00000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - -
{{metricCol.label}}
-
{{row.field}}
-
-
{{row.bucket}} -
{{row.metric}}
diff --git a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js deleted file mode 100644 index aef7bc3913a49c..00000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import $ from 'jquery'; - -import chrome from 'ui/chrome'; - -import { collectBranch } from './_collect_branch'; -import numeral from 'numeral'; -import template from './_hierarchical_tooltip.html'; - -export function HierarchicalTooltipFormatterProvider($rootScope, $compile, $sce) { - const $tooltip = $(template); - const $tooltipScope = $rootScope.$new(); - - $compile($tooltip)($tooltipScope); - - return function(metricFieldFormatter) { - return function(event) { - const datum = event.datum; - - // Collect the current leaf and parents into an array of values - $tooltipScope.rows = collectBranch(datum); - - // Map those values to what the tooltipSource.rows format. - _.forEachRight($tooltipScope.rows, function(row) { - row.spacer = $sce.trustAsHtml(_.repeat(' ', row.depth)); - - let percent; - if (row.item.percentOfGroup != null) { - percent = row.item.percentOfGroup; - } - - row.metric = metricFieldFormatter ? metricFieldFormatter.convert(row.metric) : row.metric; - - if (percent != null) { - row.metric += ' (' + numeral(percent).format('0.[00]%') + ')'; - } - - return row; - }); - - $tooltipScope.$apply(); - return $tooltip[0].outerHTML; - }; - }; -} - -let _tooltipFormatter; -export const getHierarchicalTooltipFormatter = () => { - if (!_tooltipFormatter) { - throw new Error('tooltip formatter not initialized'); - } - return _tooltipFormatter; -}; - -export const initializeHierarchicalTooltipFormatter = async () => { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); -}; - -export const setHierarchicalTooltipFormatter = Private => { - _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); -}; diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html deleted file mode 100644 index 9e82739a57f0f2..00000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -
-
{{detail.label}}
-
-
{{detail.value}} ({{detail.percent}})
-
diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js deleted file mode 100644 index 88c9e3d67b4a93..00000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; - -import chrome from 'ui/chrome'; - -import template from './_pointseries_tooltip.html'; - -export function PointSeriesTooltipFormatterProvider($compile, $rootScope) { - const $tooltipScope = $rootScope.$new(); - const $tooltip = $(template); - $compile($tooltip)($tooltipScope); - - return function() { - return function tooltipFormatter(event) { - const data = event.data; - const datum = event.datum; - if (!datum) return ''; - - const details = ($tooltipScope.details = []); - - const currentSeries = - data.series && data.series.find(serie => serie.rawId === datum.seriesId); - const addDetail = (label, value) => details.push({ label, value }); - - if (datum.extraMetrics) { - datum.extraMetrics.forEach(metric => { - addDetail(metric.label, metric.value); - }); - } - - if (datum.x) { - addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x)); - } - if (datum.y) { - const value = datum.yScale ? datum.yScale * datum.y : datum.y; - addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); - } - if (datum.z) { - addDetail(currentSeries.zLabel, currentSeries.zAxisFormatter(datum.z)); - } - if (datum.series && datum.parent) { - const dimension = datum.parent; - addDetail(dimension.title, datum.series); - } - if (datum.tableRaw) { - addDetail(datum.tableRaw.title, datum.tableRaw.value); - } - - $tooltipScope.$apply(); - return $tooltip[0].outerHTML; - }; - }; -} - -let _tooltipFormatter; -export const getPointSeriesTooltipFormatter = () => { - if (!_tooltipFormatter) { - throw new Error('tooltip formatter not initialized'); - } - return _tooltipFormatter; -}; - -export const initializePointSeriesTooltipFormatter = async () => { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); -}; - -export const setPointSeriesTooltipFormatter = Private => { - _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); -}; diff --git a/src/legacy/ui/public/vis/vis_types/_index.scss b/src/legacy/ui/public/vis/vis_types/_index.scss deleted file mode 100644 index 9d86383ec40b2e..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './vislib_vis_type'; -@import './vislib_vis_legend'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c3e46a9c04e1cc..88fd8360ec728f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -77,8 +77,6 @@ }, "messages": { "common.ui.aggResponse.allDocsTitle": "すべてのドキュメント", - "common.ui.aggResponse.fieldLabel": "フィールド", - "common.ui.aggResponse.valueLabel": "値", "common.ui.aggTypes.aggNotValidLabel": "- 無効な集約 -", "common.ui.aggTypes.aggregateWith.noAggsErrorTooltip": "選択されたフィールドには互換性のある集約がありません。", "common.ui.aggTypes.aggregateWithLabel": "アグリゲーション:", @@ -509,13 +507,6 @@ "common.ui.vis.editors.sidebar.tabs.optionsLabel": "オプション", "common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "データバウンドを合わせる", "common.ui.vis.kibanaMap.zoomWarning": "ズームレベルが最大に達しました。完全にズームインするには、Elasticsearch と Kibana の {defaultDistribution} にアップグレードしてください。{ems} でより多くのズームレベルが利用できます。または、独自のマップサーバーを構成できます。詳細は { wms } または { configSettings} をご覧ください。", - "common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", - "common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", - "common.ui.vis.visTypes.legend.loadingLabel": "読み込み中…", - "common.ui.vis.visTypes.legend.setColorScreenReaderDescription": "値 {legendDataLabel} の色を設定", - "common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", - "common.ui.vis.visTypes.legend.toggleLegendButtonTitle": "凡例を切り替える", - "common.ui.vis.visTypes.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", "common.ui.vislib.colormaps.bluesText": "青", "common.ui.vislib.colormaps.greensText": "緑", "common.ui.vislib.colormaps.greenToRedText": "緑から赤", @@ -2483,183 +2474,192 @@ "kbn.visualize.wizard.step1Breadcrumb": "作成", "kbn.visualize.wizard.step2Breadcrumb": "作成", "kbn.visualizeTitle": "可視化", - "kbnVislibVisTypes.area.areaDescription": "折れ線グラフの下の数量を強調します。", - "kbnVislibVisTypes.area.areaTitle": "エリア", - "kbnVislibVisTypes.area.countText": "カウント", - "kbnVislibVisTypes.area.groupTitle": "系列を分割", - "kbnVislibVisTypes.area.metricsTitle": "Y 軸", - "kbnVislibVisTypes.area.radiusTitle": "点のサイズ", - "kbnVislibVisTypes.area.segmentTitle": "X 軸", - "kbnVislibVisTypes.area.splitTitle": "チャートを分割", - "kbnVislibVisTypes.area.tabs.metricsAxesTitle": "メトリックと軸", - "kbnVislibVisTypes.area.tabs.panelSettingsTitle": "パネル設定", - "kbnVislibVisTypes.axisModes.normalText": "標準", - "kbnVislibVisTypes.axisModes.percentageText": "パーセンテージ", - "kbnVislibVisTypes.axisModes.silhouetteText": "シルエット", - "kbnVislibVisTypes.axisModes.wiggleText": "振動", - "kbnVislibVisTypes.categoryAxis.rotate.angledText": "傾斜", - "kbnVislibVisTypes.categoryAxis.rotate.horizontalText": "横", - "kbnVislibVisTypes.categoryAxis.rotate.verticalText": "縦", - "kbnVislibVisTypes.chartModes.normalText": "標準", - "kbnVislibVisTypes.chartModes.stackedText": "スタック", - "kbnVislibVisTypes.chartTypes.areaText": "エリア", - "kbnVislibVisTypes.chartTypes.barText": "バー", - "kbnVislibVisTypes.chartTypes.lineText": "折れ線", - "kbnVislibVisTypes.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", - "kbnVislibVisTypes.controls.colorSchema.colorSchemaLabel": "カラー図表", - "kbnVislibVisTypes.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", - "kbnVislibVisTypes.controls.colorSchema.resetColorsButtonLabel": "色をリセット", - "kbnVislibVisTypes.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", - "kbnVislibVisTypes.controls.gaugeOptions.alignmentLabel": "アラインメント", - "kbnVislibVisTypes.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", - "kbnVislibVisTypes.controls.gaugeOptions.displayWarningsLabel": "警告を表示", - "kbnVislibVisTypes.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。", - "kbnVislibVisTypes.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ", - "kbnVislibVisTypes.controls.gaugeOptions.labelsTitle": "ラベル", - "kbnVislibVisTypes.controls.gaugeOptions.percentageModeLabel": "パーセンテージモード", - "kbnVislibVisTypes.controls.gaugeOptions.rangesTitle": "範囲", - "kbnVislibVisTypes.controls.gaugeOptions.showLabelsLabel": "ラベルを表示", - "kbnVislibVisTypes.controls.gaugeOptions.showLegendLabel": "凡例を表示", - "kbnVislibVisTypes.controls.gaugeOptions.showScaleLabel": "縮尺を表示", - "kbnVislibVisTypes.controls.gaugeOptions.styleTitle": "スタイル", - "kbnVislibVisTypes.controls.gaugeOptions.subTextLabel": "サブラベル", - "kbnVislibVisTypes.controls.gaugeOptions.switchWarningsTooltip": "警告のオン・オフを切り替えます。オンにすると、すべてのラベルを表示できない際に警告が表示されます。", - "kbnVislibVisTypes.controls.heatmapOptions.colorLabel": "色", - "kbnVislibVisTypes.controls.heatmapOptions.colorScaleLabel": "カラースケール", - "kbnVislibVisTypes.controls.heatmapOptions.colorsNumberLabel": "色の数", - "kbnVislibVisTypes.controls.heatmapOptions.labelsTitle": "ラベル", - "kbnVislibVisTypes.controls.heatmapOptions.overwriteAutomaticColorLabel": "自動からーを上書きする", - "kbnVislibVisTypes.controls.heatmapOptions.percentageModeLabel": "パーセンテージモード", - "kbnVislibVisTypes.controls.heatmapOptions.rotateLabel": "回転", - "kbnVislibVisTypes.controls.heatmapOptions.scaleToDataBoundsLabel": "データバウンドに合わせる", - "kbnVislibVisTypes.controls.heatmapOptions.showLabelsTitle": "ラベルを表示", - "kbnVislibVisTypes.controls.heatmapOptions.useCustomRangesLabel": "カスタム範囲を使用", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.alignLabel": "配置", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.filterLabelsLabel": "フィルターラベル", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.labelsTitle": "ラベル", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.positionLabel": "配置", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.showLabel": "表示", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.showLabelsLabel": "ラベルを表示", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.xAxisTitle": "X 軸", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.dontShowLabel": "非表示", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.gridText": "グリッド", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.xAxisLinesLabel": "X 軸線を表示", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "ヒストグラムに X 軸線は表示できません。", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 軸線を表示", - "kbnVislibVisTypes.controls.pointSeries.series.chartTypeLabel": "チャートタイプ", - "kbnVislibVisTypes.controls.pointSeries.series.lineModeLabel": "線のモード", - "kbnVislibVisTypes.controls.pointSeries.series.lineWidthLabel": "線の幅", - "kbnVislibVisTypes.controls.pointSeries.series.metricsTitle": "メトリック", - "kbnVislibVisTypes.controls.pointSeries.series.modeLabel": "モード", - "kbnVislibVisTypes.controls.pointSeries.series.newAxisLabel": "新規軸…", - "kbnVislibVisTypes.controls.pointSeries.series.showDotsLabel": "点を表示", - "kbnVislibVisTypes.controls.pointSeries.series.showLineLabel": "線を表示", - "kbnVislibVisTypes.controls.pointSeries.series.valueAxisLabel": "値軸", - "kbnVislibVisTypes.controls.pointSeries.seriesAccordionAriaLabel": "{agg} オプションを切り替える", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.addButtonTooltip": "Y 軸を追加します", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.customExtentsLabel": "カスタム範囲", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.maxLabel": "最高", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minErrorMessage": "最低値は最高値よりも低く設定する必要があります", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minLabel": "最低", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minNeededScaleText": "ログスケールが選択されている場合、最低値は 0 よりも大きいものである必要があります", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.modeLabel": "モード", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.positionLabel": "配置", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.removeButtonTooltip": "Y 軸を削除します", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "境界マージン", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "境界マージンは 0 以上でなければなりません", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "データバウンドに合わせる", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleTypeLabel": "スケールタイプ", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.setAxisExtentsLabel": "軸の範囲の設定", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.showLabel": "表示", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.titleLabel": "タイトル", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", - "kbnVislibVisTypes.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", - "kbnVislibVisTypes.controls.truncateLabel": "切り捨て", - "kbnVislibVisTypes.controls.vislibBasicOptions.legendPositionLabel": "凡例の配置", - "kbnVislibVisTypes.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", - "kbnVislibVisTypes.editors.heatmap.basicSettingsTitle": "基本設定", - "kbnVislibVisTypes.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", - "kbnVislibVisTypes.editors.heatmap.highlightLabel": "ハイライト範囲", - "kbnVislibVisTypes.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。", - "kbnVislibVisTypes.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", - "kbnVislibVisTypes.editors.pie.donutLabel": "ドーナッツ", - "kbnVislibVisTypes.editors.pie.labelsSettingsTitle": "ラベル設定", - "kbnVislibVisTypes.editors.pie.pieSettingsTitle": "パイ設定", - "kbnVislibVisTypes.editors.pie.showLabelsLabel": "ラベルを表示", - "kbnVislibVisTypes.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", - "kbnVislibVisTypes.editors.pie.showValuesLabel": "値を表示", - "kbnVislibVisTypes.editors.pointSeries.currentTimeMarkerLabel": "現在時刻マーカー", - "kbnVislibVisTypes.editors.pointSeries.orderBucketsBySumLabel": "バケットを合計で並べ替え", - "kbnVislibVisTypes.editors.pointSeries.settingsTitle": "設定", - "kbnVislibVisTypes.editors.pointSeries.showLabels": "チャートに値を表示", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.colorLabel": "ラインカラー", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.showLabel": "しきい線を表示", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.styleLabel": "ラインスタイル", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.valueLabel": "しきい値", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.widthLabel": "線の幅", - "kbnVislibVisTypes.editors.pointSeries.thresholdLineSettingsTitle": "しきい線", - "kbnVislibVisTypes.functions.pie.help": "パイビジュアライゼーション", - "kbnVislibVisTypes.functions.vislib.help": "Vislib ビジュアライゼーション", - "kbnVislibVisTypes.gauge.alignmentAutomaticTitle": "自動", - "kbnVislibVisTypes.gauge.alignmentHorizontalTitle": "横", - "kbnVislibVisTypes.gauge.alignmentVerticalTitle": "縦", - "kbnVislibVisTypes.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値としきい値との関連性を示すのに使用します。", - "kbnVislibVisTypes.gauge.gaugeTitle": "ゲージ", - "kbnVislibVisTypes.gauge.gaugeTypes.arcText": "弧形", - "kbnVislibVisTypes.gauge.gaugeTypes.circleText": "円", - "kbnVislibVisTypes.gauge.groupTitle": "グループを分割", - "kbnVislibVisTypes.gauge.metricTitle": "メトリック", - "kbnVislibVisTypes.goal.goalDescription": "ゴールチャートは、最終目標にどれだけ近いかを示します。", - "kbnVislibVisTypes.goal.goalTitle": "ゴール", - "kbnVislibVisTypes.goal.groupTitle": "グループを分割", - "kbnVislibVisTypes.goal.metricTitle": "メトリック", - "kbnVislibVisTypes.heatmap.groupTitle": "Y 軸", - "kbnVislibVisTypes.heatmap.heatmapDescription": "マトリックス内のセルに影をつける。", - "kbnVislibVisTypes.heatmap.heatmapTitle": "ヒートマップ", - "kbnVislibVisTypes.heatmap.metricTitle": "値", - "kbnVislibVisTypes.heatmap.segmentTitle": "X 軸", - "kbnVislibVisTypes.heatmap.splitTitle": "チャートを分割", - "kbnVislibVisTypes.histogram.groupTitle": "系列を分割", - "kbnVislibVisTypes.histogram.histogramDescription": "連続変数を各軸に割り当てる。", - "kbnVislibVisTypes.histogram.histogramTitle": "縦棒", - "kbnVislibVisTypes.histogram.metricTitle": "Y 軸", - "kbnVislibVisTypes.histogram.radiusTitle": "点のサイズ", - "kbnVislibVisTypes.histogram.segmentTitle": "X 軸", - "kbnVislibVisTypes.histogram.splitTitle": "チャートを分割", - "kbnVislibVisTypes.horizontalBar.groupTitle": "系列を分割", - "kbnVislibVisTypes.horizontalBar.horizontalBarDescription": "連続変数を各軸に割り当てる。", - "kbnVislibVisTypes.horizontalBar.horizontalBarTitle": "横棒", - "kbnVislibVisTypes.horizontalBar.metricTitle": "Y 軸", - "kbnVislibVisTypes.horizontalBar.radiusTitle": "点のサイズ", - "kbnVislibVisTypes.horizontalBar.segmentTitle": "X 軸", - "kbnVislibVisTypes.horizontalBar.splitTitle": "チャートを分割", - "kbnVislibVisTypes.interpolationModes.smoothedText": "スムーズ", - "kbnVislibVisTypes.interpolationModes.steppedText": "ステップ", - "kbnVislibVisTypes.interpolationModes.straightText": "直線", - "kbnVislibVisTypes.legendPositions.bottomText": "一番下", - "kbnVislibVisTypes.legendPositions.leftText": "左", - "kbnVislibVisTypes.legendPositions.rightText": "右", - "kbnVislibVisTypes.legendPositions.topText": "一番上", - "kbnVislibVisTypes.line.groupTitle": "系列を分割", - "kbnVislibVisTypes.line.lineDescription": "トレンドを強調します。", - "kbnVislibVisTypes.line.lineTitle": "折れ線", - "kbnVislibVisTypes.line.metricTitle": "Y 軸", - "kbnVislibVisTypes.line.radiusTitle": "点のサイズ", - "kbnVislibVisTypes.line.segmentTitle": "X 軸", - "kbnVislibVisTypes.line.splitTitle": "チャートを分割", - "kbnVislibVisTypes.pie.metricTitle": "サイズのスライス", - "kbnVislibVisTypes.pie.pieDescription": "全体に対する内訳を表現する。", - "kbnVislibVisTypes.pie.pieTitle": "パイ", - "kbnVislibVisTypes.pie.segmentTitle": "スライスの分割", - "kbnVislibVisTypes.pie.splitTitle": "チャートを分割", - "kbnVislibVisTypes.scaleTypes.linearText": "直線", - "kbnVislibVisTypes.scaleTypes.logText": "ログ", - "kbnVislibVisTypes.scaleTypes.squareRootText": "平方根", - "kbnVislibVisTypes.thresholdLine.style.dashedText": "鎖線", - "kbnVislibVisTypes.thresholdLine.style.dotdashedText": "点線", - "kbnVislibVisTypes.thresholdLine.style.fullText": "完全", + "visTypeVislib.area.areaDescription": "折れ線グラフの下の数量を強調します。", + "visTypeVislib.area.areaTitle": "エリア", + "visTypeVislib.area.countText": "カウント", + "visTypeVislib.area.groupTitle": "系列を分割", + "visTypeVislib.area.metricsTitle": "Y 軸", + "visTypeVislib.area.radiusTitle": "点のサイズ", + "visTypeVislib.area.segmentTitle": "X 軸", + "visTypeVislib.area.splitTitle": "チャートを分割", + "visTypeVislib.area.tabs.metricsAxesTitle": "メトリックと軸", + "visTypeVislib.area.tabs.panelSettingsTitle": "パネル設定", + "visTypeVislib.axisModes.normalText": "標準", + "visTypeVislib.axisModes.percentageText": "パーセンテージ", + "visTypeVislib.axisModes.silhouetteText": "シルエット", + "visTypeVislib.axisModes.wiggleText": "振動", + "visTypeVislib.categoryAxis.rotate.angledText": "傾斜", + "visTypeVislib.categoryAxis.rotate.horizontalText": "横", + "visTypeVislib.categoryAxis.rotate.verticalText": "縦", + "visTypeVislib.chartModes.normalText": "標準", + "visTypeVislib.chartModes.stackedText": "スタック", + "visTypeVislib.chartTypes.areaText": "エリア", + "visTypeVislib.chartTypes.barText": "バー", + "visTypeVislib.chartTypes.lineText": "折れ線", + "visTypeVislib.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", + "visTypeVislib.controls.colorSchema.colorSchemaLabel": "カラー図表", + "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", + "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "色をリセット", + "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", + "visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント", + "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", + "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示", + "visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。", + "visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ", + "visTypeVislib.controls.gaugeOptions.labelsTitle": "ラベル", + "visTypeVislib.controls.gaugeOptions.percentageModeLabel": "パーセンテージモード", + "visTypeVislib.controls.gaugeOptions.rangesTitle": "範囲", + "visTypeVislib.controls.gaugeOptions.showLabelsLabel": "ラベルを表示", + "visTypeVislib.controls.gaugeOptions.showLegendLabel": "凡例を表示", + "visTypeVislib.controls.gaugeOptions.showScaleLabel": "縮尺を表示", + "visTypeVislib.controls.gaugeOptions.styleTitle": "スタイル", + "visTypeVislib.controls.gaugeOptions.subTextLabel": "サブラベル", + "visTypeVislib.controls.gaugeOptions.switchWarningsTooltip": "警告のオン・オフを切り替えます。オンにすると、すべてのラベルを表示できない際に警告が表示されます。", + "visTypeVislib.controls.heatmapOptions.colorLabel": "色", + "visTypeVislib.controls.heatmapOptions.colorScaleLabel": "カラースケール", + "visTypeVislib.controls.heatmapOptions.colorsNumberLabel": "色の数", + "visTypeVislib.controls.heatmapOptions.labelsTitle": "ラベル", + "visTypeVislib.controls.heatmapOptions.overwriteAutomaticColorLabel": "自動からーを上書きする", + "visTypeVislib.controls.heatmapOptions.percentageModeLabel": "パーセンテージモード", + "visTypeVislib.controls.heatmapOptions.rotateLabel": "回転", + "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "データバウンドに合わせる", + "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "ラベルを表示", + "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "カスタム範囲を使用", + "visTypeVislib.controls.pointSeries.categoryAxis.alignLabel": "配置", + "visTypeVislib.controls.pointSeries.categoryAxis.filterLabelsLabel": "フィルターラベル", + "visTypeVislib.controls.pointSeries.categoryAxis.labelsTitle": "ラベル", + "visTypeVislib.controls.pointSeries.categoryAxis.positionLabel": "配置", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabel": "表示", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabelsLabel": "ラベルを表示", + "visTypeVislib.controls.pointSeries.categoryAxis.xAxisTitle": "X 軸", + "visTypeVislib.controls.pointSeries.gridAxis.dontShowLabel": "非表示", + "visTypeVislib.controls.pointSeries.gridAxis.gridText": "グリッド", + "visTypeVislib.controls.pointSeries.gridAxis.xAxisLinesLabel": "X 軸線を表示", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "ヒストグラムに X 軸線は表示できません。", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 軸線を表示", + "visTypeVislib.controls.pointSeries.series.chartTypeLabel": "チャートタイプ", + "visTypeVislib.controls.pointSeries.series.lineModeLabel": "線のモード", + "visTypeVislib.controls.pointSeries.series.lineWidthLabel": "線の幅", + "visTypeVislib.controls.pointSeries.series.metricsTitle": "メトリック", + "visTypeVislib.controls.pointSeries.series.modeLabel": "モード", + "visTypeVislib.controls.pointSeries.series.newAxisLabel": "新規軸…", + "visTypeVislib.controls.pointSeries.series.showDotsLabel": "点を表示", + "visTypeVislib.controls.pointSeries.series.showLineLabel": "線を表示", + "visTypeVislib.controls.pointSeries.series.valueAxisLabel": "値軸", + "visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel": "{agg} オプションを切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip": "Y 軸を追加します", + "visTypeVislib.controls.pointSeries.valueAxes.customExtentsLabel": "カスタム範囲", + "visTypeVislib.controls.pointSeries.valueAxes.maxLabel": "最高", + "visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage": "最低値は最高値よりも低く設定する必要があります", + "visTypeVislib.controls.pointSeries.valueAxes.minLabel": "最低", + "visTypeVislib.controls.pointSeries.valueAxes.minNeededScaleText": "ログスケールが選択されている場合、最低値は 0 よりも大きいものである必要があります", + "visTypeVislib.controls.pointSeries.valueAxes.modeLabel": "モード", + "visTypeVislib.controls.pointSeries.valueAxes.positionLabel": "配置", + "visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip": "Y 軸を削除します", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "境界マージン", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "境界マージンは 0 以上でなければなりません", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "データバウンドに合わせる", + "visTypeVislib.controls.pointSeries.valueAxes.scaleTypeLabel": "スケールタイプ", + "visTypeVislib.controls.pointSeries.valueAxes.setAxisExtentsLabel": "軸の範囲の設定", + "visTypeVislib.controls.pointSeries.valueAxes.showLabel": "表示", + "visTypeVislib.controls.pointSeries.valueAxes.titleLabel": "タイトル", + "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", + "visTypeVislib.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", + "visTypeVislib.controls.truncateLabel": "切り捨て", + "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "凡例の配置", + "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", + "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", + "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", + "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", + "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。", + "visTypeVislib.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", + "visTypeVislib.editors.pie.donutLabel": "ドーナッツ", + "visTypeVislib.editors.pie.labelsSettingsTitle": "ラベル設定", + "visTypeVislib.editors.pie.pieSettingsTitle": "パイ設定", + "visTypeVislib.editors.pie.showLabelsLabel": "ラベルを表示", + "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", + "visTypeVislib.editors.pie.showValuesLabel": "値を表示", + "visTypeVislib.editors.pointSeries.currentTimeMarkerLabel": "現在時刻マーカー", + "visTypeVislib.editors.pointSeries.orderBucketsBySumLabel": "バケットを合計で並べ替え", + "visTypeVislib.editors.pointSeries.settingsTitle": "設定", + "visTypeVislib.editors.pointSeries.showLabels": "チャートに値を表示", + "visTypeVislib.editors.pointSeries.thresholdLine.colorLabel": "ラインカラー", + "visTypeVislib.editors.pointSeries.thresholdLine.showLabel": "しきい線を表示", + "visTypeVislib.editors.pointSeries.thresholdLine.styleLabel": "ラインスタイル", + "visTypeVislib.editors.pointSeries.thresholdLine.valueLabel": "しきい値", + "visTypeVislib.editors.pointSeries.thresholdLine.widthLabel": "線の幅", + "visTypeVislib.editors.pointSeries.thresholdLineSettingsTitle": "しきい線", + "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", + "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", + "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", + "visTypeVislib.gauge.alignmentHorizontalTitle": "横", + "visTypeVislib.gauge.alignmentVerticalTitle": "縦", + "visTypeVislib.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値としきい値との関連性を示すのに使用します。", + "visTypeVislib.gauge.gaugeTitle": "ゲージ", + "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", + "visTypeVislib.gauge.gaugeTypes.circleText": "円", + "visTypeVislib.gauge.groupTitle": "グループを分割", + "visTypeVislib.gauge.metricTitle": "メトリック", + "visTypeVislib.goal.goalDescription": "ゴールチャートは、最終目標にどれだけ近いかを示します。", + "visTypeVislib.goal.goalTitle": "ゴール", + "visTypeVislib.goal.groupTitle": "グループを分割", + "visTypeVislib.goal.metricTitle": "メトリック", + "visTypeVislib.heatmap.groupTitle": "Y 軸", + "visTypeVislib.heatmap.heatmapDescription": "マトリックス内のセルに影をつける。", + "visTypeVislib.heatmap.heatmapTitle": "ヒートマップ", + "visTypeVislib.heatmap.metricTitle": "値", + "visTypeVislib.heatmap.segmentTitle": "X 軸", + "visTypeVislib.heatmap.splitTitle": "チャートを分割", + "visTypeVislib.histogram.groupTitle": "系列を分割", + "visTypeVislib.histogram.histogramDescription": "連続変数を各軸に割り当てる。", + "visTypeVislib.histogram.histogramTitle": "縦棒", + "visTypeVislib.histogram.metricTitle": "Y 軸", + "visTypeVislib.histogram.radiusTitle": "点のサイズ", + "visTypeVislib.histogram.segmentTitle": "X 軸", + "visTypeVislib.histogram.splitTitle": "チャートを分割", + "visTypeVislib.horizontalBar.groupTitle": "系列を分割", + "visTypeVislib.horizontalBar.horizontalBarDescription": "連続変数を各軸に割り当てる。", + "visTypeVislib.horizontalBar.horizontalBarTitle": "横棒", + "visTypeVislib.horizontalBar.metricTitle": "Y 軸", + "visTypeVislib.horizontalBar.radiusTitle": "点のサイズ", + "visTypeVislib.horizontalBar.segmentTitle": "X 軸", + "visTypeVislib.horizontalBar.splitTitle": "チャートを分割", + "visTypeVislib.interpolationModes.smoothedText": "スムーズ", + "visTypeVislib.interpolationModes.steppedText": "ステップ", + "visTypeVislib.interpolationModes.straightText": "直線", + "visTypeVislib.legendPositions.bottomText": "一番下", + "visTypeVislib.legendPositions.leftText": "左", + "visTypeVislib.legendPositions.rightText": "右", + "visTypeVislib.legendPositions.topText": "一番上", + "visTypeVislib.line.groupTitle": "系列を分割", + "visTypeVislib.line.lineDescription": "トレンドを強調します。", + "visTypeVislib.line.lineTitle": "折れ線", + "visTypeVislib.line.metricTitle": "Y 軸", + "visTypeVislib.line.radiusTitle": "点のサイズ", + "visTypeVislib.line.segmentTitle": "X 軸", + "visTypeVislib.line.splitTitle": "チャートを分割", + "visTypeVislib.pie.metricTitle": "サイズのスライス", + "visTypeVislib.pie.pieDescription": "全体に対する内訳を表現する。", + "visTypeVislib.pie.pieTitle": "パイ", + "visTypeVislib.pie.segmentTitle": "スライスの分割", + "visTypeVislib.pie.splitTitle": "チャートを分割", + "visTypeVislib.scaleTypes.linearText": "直線", + "visTypeVislib.scaleTypes.logText": "ログ", + "visTypeVislib.scaleTypes.squareRootText": "平方根", + "visTypeVislib.thresholdLine.style.dashedText": "鎖線", + "visTypeVislib.thresholdLine.style.dotdashedText": "点線", + "visTypeVislib.thresholdLine.style.fullText": "完全", + "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", + "visTypeVislib.vislib.tooltip.valueLabel": "値", + "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", + "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", + "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", + "visTypeVislib.vislib.legend.setColorScreenReaderDescription": "値 {legendDataLabel} の色を設定", + "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "全画面を終了", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "ESC キーで全画面モードを終了します。", "kibana-react.savedObjects.finder.filterButtonLabel": "タイプ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c40b8c8d0393c8..ad62993d50f062 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -77,8 +77,6 @@ }, "messages": { "common.ui.aggResponse.allDocsTitle": "所有文档", - "common.ui.aggResponse.fieldLabel": "字段", - "common.ui.aggResponse.valueLabel": "值", "common.ui.aggTypes.aggNotValidLabel": "- 聚合无效 -", "common.ui.aggTypes.aggregateWith.noAggsErrorTooltip": "选择的字段没有兼容的聚合。", "common.ui.aggTypes.aggregateWithLabel": "聚合对象", @@ -509,13 +507,6 @@ "common.ui.vis.editors.sidebar.tabs.optionsLabel": "选项", "common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "适应数据边界", "common.ui.vis.kibanaMap.zoomWarning": "已达到缩放级别最大数目。要一直放大,请升级到 Elasticsearch 和 Kibana 的 {defaultDistribution}。您可以通过 {ems} 免费使用其他缩放级别。或者,您可以配置自己的地图服务器。请前往 { wms } 或 { configSettings} 以获取详细信息。", - "common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", - "common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel": "筛除值 {legendDataLabel}", - "common.ui.vis.visTypes.legend.loadingLabel": "正在加载……", - "common.ui.vis.visTypes.legend.setColorScreenReaderDescription": "为值 {legendDataLabel} 设置颜色", - "common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel": "切换图例", - "common.ui.vis.visTypes.legend.toggleLegendButtonTitle": "切换图例", - "common.ui.vis.visTypes.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}切换选项", "common.ui.vislib.colormaps.bluesText": "蓝色", "common.ui.vislib.colormaps.greensText": "绿色", "common.ui.vislib.colormaps.greenToRedText": "绿到红", @@ -2483,183 +2474,192 @@ "kbn.visualize.wizard.step1Breadcrumb": "创建", "kbn.visualize.wizard.step2Breadcrumb": "创建", "kbn.visualizeTitle": "可视化", - "kbnVislibVisTypes.area.areaDescription": "突出折线图下方的数量", - "kbnVislibVisTypes.area.areaTitle": "面积图", - "kbnVislibVisTypes.area.countText": "计数", - "kbnVislibVisTypes.area.groupTitle": "拆分序列", - "kbnVislibVisTypes.area.metricsTitle": "Y 轴", - "kbnVislibVisTypes.area.radiusTitle": "点大小", - "kbnVislibVisTypes.area.segmentTitle": "X 轴", - "kbnVislibVisTypes.area.splitTitle": "拆分图表", - "kbnVislibVisTypes.area.tabs.metricsAxesTitle": "指标和轴", - "kbnVislibVisTypes.area.tabs.panelSettingsTitle": "面板设置", - "kbnVislibVisTypes.axisModes.normalText": "正常", - "kbnVislibVisTypes.axisModes.percentageText": "百分比", - "kbnVislibVisTypes.axisModes.silhouetteText": "剪影", - "kbnVislibVisTypes.axisModes.wiggleText": "扭动", - "kbnVislibVisTypes.categoryAxis.rotate.angledText": "带角度", - "kbnVislibVisTypes.categoryAxis.rotate.horizontalText": "水平", - "kbnVislibVisTypes.categoryAxis.rotate.verticalText": "垂直", - "kbnVislibVisTypes.chartModes.normalText": "正常", - "kbnVislibVisTypes.chartModes.stackedText": "堆叠", - "kbnVislibVisTypes.chartTypes.areaText": "面积图", - "kbnVislibVisTypes.chartTypes.barText": "条形图", - "kbnVislibVisTypes.chartTypes.lineText": "折线图", - "kbnVislibVisTypes.controls.colorRanges.errorText": "每个范围应大于前一范围。", - "kbnVislibVisTypes.controls.colorSchema.colorSchemaLabel": "颜色模式", - "kbnVislibVisTypes.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。", - "kbnVislibVisTypes.controls.colorSchema.resetColorsButtonLabel": "重置颜色", - "kbnVislibVisTypes.controls.colorSchema.reverseColorSchemaLabel": "反转模式", - "kbnVislibVisTypes.controls.gaugeOptions.alignmentLabel": "对齐方式", - "kbnVislibVisTypes.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围", - "kbnVislibVisTypes.controls.gaugeOptions.displayWarningsLabel": "显示警告", - "kbnVislibVisTypes.controls.gaugeOptions.extendRangeTooltip": "将数据范围扩展到最大值。", - "kbnVislibVisTypes.controls.gaugeOptions.gaugeTypeLabel": "仪表类型", - "kbnVislibVisTypes.controls.gaugeOptions.labelsTitle": "标签", - "kbnVislibVisTypes.controls.gaugeOptions.percentageModeLabel": "百分比模式", - "kbnVislibVisTypes.controls.gaugeOptions.rangesTitle": "范围", - "kbnVislibVisTypes.controls.gaugeOptions.showLabelsLabel": "显示标签", - "kbnVislibVisTypes.controls.gaugeOptions.showLegendLabel": "显示图例", - "kbnVislibVisTypes.controls.gaugeOptions.showScaleLabel": "显示比例", - "kbnVislibVisTypes.controls.gaugeOptions.styleTitle": "样式", - "kbnVislibVisTypes.controls.gaugeOptions.subTextLabel": "子标签", - "kbnVislibVisTypes.controls.gaugeOptions.switchWarningsTooltip": "打开/关闭警告。打开时,如果标签没有全部显示,则显示警告。", - "kbnVislibVisTypes.controls.heatmapOptions.colorLabel": "颜色", - "kbnVislibVisTypes.controls.heatmapOptions.colorScaleLabel": "色阶", - "kbnVislibVisTypes.controls.heatmapOptions.colorsNumberLabel": "颜色个数", - "kbnVislibVisTypes.controls.heatmapOptions.labelsTitle": "标签", - "kbnVislibVisTypes.controls.heatmapOptions.overwriteAutomaticColorLabel": "覆盖自动配色", - "kbnVislibVisTypes.controls.heatmapOptions.percentageModeLabel": "百分比模式", - "kbnVislibVisTypes.controls.heatmapOptions.rotateLabel": "旋转", - "kbnVislibVisTypes.controls.heatmapOptions.scaleToDataBoundsLabel": "缩放到数据边界", - "kbnVislibVisTypes.controls.heatmapOptions.showLabelsTitle": "显示标签", - "kbnVislibVisTypes.controls.heatmapOptions.useCustomRangesLabel": "使用定制范围", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.alignLabel": "对齐", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.filterLabelsLabel": "筛选标签", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.labelsTitle": "标签", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.positionLabel": "位置", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.showLabel": "显示", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.showLabelsLabel": "显示标签", - "kbnVislibVisTypes.controls.pointSeries.categoryAxis.xAxisTitle": "X 轴", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.dontShowLabel": "不显示", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.gridText": "网格", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.xAxisLinesLabel": "显示 X 轴线", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "直方图的 X 轴线无法显示。", - "kbnVislibVisTypes.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 轴线", - "kbnVislibVisTypes.controls.pointSeries.series.chartTypeLabel": "图表类型", - "kbnVislibVisTypes.controls.pointSeries.series.lineModeLabel": "线条模式", - "kbnVislibVisTypes.controls.pointSeries.series.lineWidthLabel": "线条宽度", - "kbnVislibVisTypes.controls.pointSeries.series.metricsTitle": "指标", - "kbnVislibVisTypes.controls.pointSeries.series.modeLabel": "模式", - "kbnVislibVisTypes.controls.pointSeries.series.newAxisLabel": "新建轴…...", - "kbnVislibVisTypes.controls.pointSeries.series.showDotsLabel": "显示点线", - "kbnVislibVisTypes.controls.pointSeries.series.showLineLabel": "显示为线条", - "kbnVislibVisTypes.controls.pointSeries.series.valueAxisLabel": "值轴", - "kbnVislibVisTypes.controls.pointSeries.seriesAccordionAriaLabel": "切换 {agg} 选项", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.addButtonTooltip": "添加 Y 轴", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.customExtentsLabel": "定制范围", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.maxLabel": "最大值", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minErrorMessage": "最小值应小于最大值。", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minLabel": "最小值", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.minNeededScaleText": "如果选择了对数刻度,最小值必须大于 0。", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.modeLabel": "模式", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.positionLabel": "位置", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.removeButtonTooltip": "移除 Y 轴", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "边界边距", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "边界边距必须大于或等于 0。", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "缩放到数据边界", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleTypeLabel": "缩放类型", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.setAxisExtentsLabel": "设置轴范围", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.showLabel": "显示", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.titleLabel": "标题", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "切换定制范围", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项", - "kbnVislibVisTypes.controls.pointSeries.valueAxes.yAxisTitle": "Y 轴", - "kbnVislibVisTypes.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", - "kbnVislibVisTypes.controls.truncateLabel": "截断", - "kbnVislibVisTypes.controls.vislibBasicOptions.legendPositionLabel": "图例位置", - "kbnVislibVisTypes.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", - "kbnVislibVisTypes.editors.heatmap.basicSettingsTitle": "基本设置", - "kbnVislibVisTypes.editors.heatmap.heatmapSettingsTitle": "热图设置", - "kbnVislibVisTypes.editors.heatmap.highlightLabel": "高亮范围", - "kbnVislibVisTypes.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", - "kbnVislibVisTypes.editors.heatmap.highlightLabelTooltip": "高亮显示图表中鼠标悬停的范围以及图例中对应的标签。", - "kbnVislibVisTypes.editors.pie.donutLabel": "圆环图", - "kbnVislibVisTypes.editors.pie.labelsSettingsTitle": "标签设置", - "kbnVislibVisTypes.editors.pie.pieSettingsTitle": "饼图设置", - "kbnVislibVisTypes.editors.pie.showLabelsLabel": "显示标签", - "kbnVislibVisTypes.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", - "kbnVislibVisTypes.editors.pie.showValuesLabel": "显示值", - "kbnVislibVisTypes.editors.pointSeries.currentTimeMarkerLabel": "当前时间标记", - "kbnVislibVisTypes.editors.pointSeries.orderBucketsBySumLabel": "按总计值排序存储桶", - "kbnVislibVisTypes.editors.pointSeries.settingsTitle": "设置", - "kbnVislibVisTypes.editors.pointSeries.showLabels": "在图表上显示值", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.colorLabel": "线条颜色", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.showLabel": "显示阈值线条", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.styleLabel": "线条样式", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.valueLabel": "阈值", - "kbnVislibVisTypes.editors.pointSeries.thresholdLine.widthLabel": "线条宽度", - "kbnVislibVisTypes.editors.pointSeries.thresholdLineSettingsTitle": "阈值线条", - "kbnVislibVisTypes.functions.pie.help": "饼图可视化", - "kbnVislibVisTypes.functions.vislib.help": "Vislib 可视化", - "kbnVislibVisTypes.gauge.alignmentAutomaticTitle": "自动", - "kbnVislibVisTypes.gauge.alignmentHorizontalTitle": "水平", - "kbnVislibVisTypes.gauge.alignmentVerticalTitle": "垂直", - "kbnVislibVisTypes.gauge.gaugeDescription": "仪表盘图指示指标的状态。用于显示指标值与参考阈值的相关程度。", - "kbnVislibVisTypes.gauge.gaugeTitle": "仪表盘图", - "kbnVislibVisTypes.gauge.gaugeTypes.arcText": "弧形", - "kbnVislibVisTypes.gauge.gaugeTypes.circleText": "圆形", - "kbnVislibVisTypes.gauge.groupTitle": "拆分组", - "kbnVislibVisTypes.gauge.metricTitle": "指标", - "kbnVislibVisTypes.goal.goalDescription": "目标图指示与最终目标的接近程度。", - "kbnVislibVisTypes.goal.goalTitle": "目标图", - "kbnVislibVisTypes.goal.groupTitle": "拆分组", - "kbnVislibVisTypes.goal.metricTitle": "指标", - "kbnVislibVisTypes.heatmap.groupTitle": "Y 轴", - "kbnVislibVisTypes.heatmap.heatmapDescription": "为矩阵中的单元格添加阴影", - "kbnVislibVisTypes.heatmap.heatmapTitle": "热力图", - "kbnVislibVisTypes.heatmap.metricTitle": "值", - "kbnVislibVisTypes.heatmap.segmentTitle": "X 轴", - "kbnVislibVisTypes.heatmap.splitTitle": "拆分图表", - "kbnVislibVisTypes.histogram.groupTitle": "拆分序列", - "kbnVislibVisTypes.histogram.histogramDescription": "向每个轴赋予连续变量", - "kbnVislibVisTypes.histogram.histogramTitle": "垂直条形图", - "kbnVislibVisTypes.histogram.metricTitle": "Y 轴", - "kbnVislibVisTypes.histogram.radiusTitle": "点大小", - "kbnVislibVisTypes.histogram.segmentTitle": "X 轴", - "kbnVislibVisTypes.histogram.splitTitle": "拆分图表", - "kbnVislibVisTypes.horizontalBar.groupTitle": "拆分序列", - "kbnVislibVisTypes.horizontalBar.horizontalBarDescription": "向每个轴赋予连续变量", - "kbnVislibVisTypes.horizontalBar.horizontalBarTitle": "水平条形图", - "kbnVislibVisTypes.horizontalBar.metricTitle": "Y 轴", - "kbnVislibVisTypes.horizontalBar.radiusTitle": "点大小", - "kbnVislibVisTypes.horizontalBar.segmentTitle": "X 轴", - "kbnVislibVisTypes.horizontalBar.splitTitle": "拆分图表", - "kbnVislibVisTypes.interpolationModes.smoothedText": "平滑", - "kbnVislibVisTypes.interpolationModes.steppedText": "渐变", - "kbnVislibVisTypes.interpolationModes.straightText": "直线", - "kbnVislibVisTypes.legendPositions.bottomText": "下", - "kbnVislibVisTypes.legendPositions.leftText": "左", - "kbnVislibVisTypes.legendPositions.rightText": "右", - "kbnVislibVisTypes.legendPositions.topText": "上", - "kbnVislibVisTypes.line.groupTitle": "拆分序列", - "kbnVislibVisTypes.line.lineDescription": "突出趋势", - "kbnVislibVisTypes.line.lineTitle": "折线图", - "kbnVislibVisTypes.line.metricTitle": "Y 轴", - "kbnVislibVisTypes.line.radiusTitle": "点大小", - "kbnVislibVisTypes.line.segmentTitle": "X 轴", - "kbnVislibVisTypes.line.splitTitle": "拆分图表", - "kbnVislibVisTypes.pie.metricTitle": "切片大小", - "kbnVislibVisTypes.pie.pieDescription": "比较整体的各个部分", - "kbnVislibVisTypes.pie.pieTitle": "饼图", - "kbnVislibVisTypes.pie.segmentTitle": "拆分切片", - "kbnVislibVisTypes.pie.splitTitle": "拆分图表", - "kbnVislibVisTypes.scaleTypes.linearText": "线性", - "kbnVislibVisTypes.scaleTypes.logText": "对数", - "kbnVislibVisTypes.scaleTypes.squareRootText": "平方根", - "kbnVislibVisTypes.thresholdLine.style.dashedText": "虚线", - "kbnVislibVisTypes.thresholdLine.style.dotdashedText": "点虚线", - "kbnVislibVisTypes.thresholdLine.style.fullText": "实线", + "visTypeVislib.area.areaDescription": "突出折线图下方的数量", + "visTypeVislib.area.areaTitle": "面积图", + "visTypeVislib.area.countText": "计数", + "visTypeVislib.area.groupTitle": "拆分序列", + "visTypeVislib.area.metricsTitle": "Y 轴", + "visTypeVislib.area.radiusTitle": "点大小", + "visTypeVislib.area.segmentTitle": "X 轴", + "visTypeVislib.area.splitTitle": "拆分图表", + "visTypeVislib.area.tabs.metricsAxesTitle": "指标和轴", + "visTypeVislib.area.tabs.panelSettingsTitle": "面板设置", + "visTypeVislib.axisModes.normalText": "正常", + "visTypeVislib.axisModes.percentageText": "百分比", + "visTypeVislib.axisModes.silhouetteText": "剪影", + "visTypeVislib.axisModes.wiggleText": "扭动", + "visTypeVislib.categoryAxis.rotate.angledText": "带角度", + "visTypeVislib.categoryAxis.rotate.horizontalText": "水平", + "visTypeVislib.categoryAxis.rotate.verticalText": "垂直", + "visTypeVislib.chartModes.normalText": "正常", + "visTypeVislib.chartModes.stackedText": "堆叠", + "visTypeVislib.chartTypes.areaText": "面积图", + "visTypeVislib.chartTypes.barText": "条形图", + "visTypeVislib.chartTypes.lineText": "折线图", + "visTypeVislib.controls.colorRanges.errorText": "每个范围应大于前一范围。", + "visTypeVislib.controls.colorSchema.colorSchemaLabel": "颜色模式", + "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。", + "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "重置颜色", + "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "反转模式", + "visTypeVislib.controls.gaugeOptions.alignmentLabel": "对齐方式", + "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围", + "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "显示警告", + "visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "将数据范围扩展到最大值。", + "visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "仪表类型", + "visTypeVislib.controls.gaugeOptions.labelsTitle": "标签", + "visTypeVislib.controls.gaugeOptions.percentageModeLabel": "百分比模式", + "visTypeVislib.controls.gaugeOptions.rangesTitle": "范围", + "visTypeVislib.controls.gaugeOptions.showLabelsLabel": "显示标签", + "visTypeVislib.controls.gaugeOptions.showLegendLabel": "显示图例", + "visTypeVislib.controls.gaugeOptions.showScaleLabel": "显示比例", + "visTypeVislib.controls.gaugeOptions.styleTitle": "样式", + "visTypeVislib.controls.gaugeOptions.subTextLabel": "子标签", + "visTypeVislib.controls.gaugeOptions.switchWarningsTooltip": "打开/关闭警告。打开时,如果标签没有全部显示,则显示警告。", + "visTypeVislib.controls.heatmapOptions.colorLabel": "颜色", + "visTypeVislib.controls.heatmapOptions.colorScaleLabel": "色阶", + "visTypeVislib.controls.heatmapOptions.colorsNumberLabel": "颜色个数", + "visTypeVislib.controls.heatmapOptions.labelsTitle": "标签", + "visTypeVislib.controls.heatmapOptions.overwriteAutomaticColorLabel": "覆盖自动配色", + "visTypeVislib.controls.heatmapOptions.percentageModeLabel": "百分比模式", + "visTypeVislib.controls.heatmapOptions.rotateLabel": "旋转", + "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "缩放到数据边界", + "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "显示标签", + "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "使用定制范围", + "visTypeVislib.controls.pointSeries.categoryAxis.alignLabel": "对齐", + "visTypeVislib.controls.pointSeries.categoryAxis.filterLabelsLabel": "筛选标签", + "visTypeVislib.controls.pointSeries.categoryAxis.labelsTitle": "标签", + "visTypeVislib.controls.pointSeries.categoryAxis.positionLabel": "位置", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabel": "显示", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabelsLabel": "显示标签", + "visTypeVislib.controls.pointSeries.categoryAxis.xAxisTitle": "X 轴", + "visTypeVislib.controls.pointSeries.gridAxis.dontShowLabel": "不显示", + "visTypeVislib.controls.pointSeries.gridAxis.gridText": "网格", + "visTypeVislib.controls.pointSeries.gridAxis.xAxisLinesLabel": "显示 X 轴线", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "直方图的 X 轴线无法显示。", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 轴线", + "visTypeVislib.controls.pointSeries.series.chartTypeLabel": "图表类型", + "visTypeVislib.controls.pointSeries.series.lineModeLabel": "线条模式", + "visTypeVislib.controls.pointSeries.series.lineWidthLabel": "线条宽度", + "visTypeVislib.controls.pointSeries.series.metricsTitle": "指标", + "visTypeVislib.controls.pointSeries.series.modeLabel": "模式", + "visTypeVislib.controls.pointSeries.series.newAxisLabel": "新建轴…...", + "visTypeVislib.controls.pointSeries.series.showDotsLabel": "显示点线", + "visTypeVislib.controls.pointSeries.series.showLineLabel": "显示为线条", + "visTypeVislib.controls.pointSeries.series.valueAxisLabel": "值轴", + "visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel": "切换 {agg} 选项", + "visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip": "添加 Y 轴", + "visTypeVislib.controls.pointSeries.valueAxes.customExtentsLabel": "定制范围", + "visTypeVislib.controls.pointSeries.valueAxes.maxLabel": "最大值", + "visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage": "最小值应小于最大值。", + "visTypeVislib.controls.pointSeries.valueAxes.minLabel": "最小值", + "visTypeVislib.controls.pointSeries.valueAxes.minNeededScaleText": "如果选择了对数刻度,最小值必须大于 0。", + "visTypeVislib.controls.pointSeries.valueAxes.modeLabel": "模式", + "visTypeVislib.controls.pointSeries.valueAxes.positionLabel": "位置", + "visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip": "移除 Y 轴", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "边界边距", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "边界边距必须大于或等于 0。", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "缩放到数据边界", + "visTypeVislib.controls.pointSeries.valueAxes.scaleTypeLabel": "缩放类型", + "visTypeVislib.controls.pointSeries.valueAxes.setAxisExtentsLabel": "设置轴范围", + "visTypeVislib.controls.pointSeries.valueAxes.showLabel": "显示", + "visTypeVislib.controls.pointSeries.valueAxes.titleLabel": "标题", + "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "切换定制范围", + "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项", + "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 轴", + "visTypeVislib.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", + "visTypeVislib.controls.truncateLabel": "截断", + "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "图例位置", + "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", + "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本设置", + "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "热图设置", + "visTypeVislib.editors.heatmap.highlightLabel": "高亮范围", + "visTypeVislib.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", + "visTypeVislib.editors.heatmap.highlightLabelTooltip": "高亮显示图表中鼠标悬停的范围以及图例中对应的标签。", + "visTypeVislib.editors.pie.donutLabel": "圆环图", + "visTypeVislib.editors.pie.labelsSettingsTitle": "标签设置", + "visTypeVislib.editors.pie.pieSettingsTitle": "饼图设置", + "visTypeVislib.editors.pie.showLabelsLabel": "显示标签", + "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", + "visTypeVislib.editors.pie.showValuesLabel": "显示值", + "visTypeVislib.editors.pointSeries.currentTimeMarkerLabel": "当前时间标记", + "visTypeVislib.editors.pointSeries.orderBucketsBySumLabel": "按总计值排序存储桶", + "visTypeVislib.editors.pointSeries.settingsTitle": "设置", + "visTypeVislib.editors.pointSeries.showLabels": "在图表上显示值", + "visTypeVislib.editors.pointSeries.thresholdLine.colorLabel": "线条颜色", + "visTypeVislib.editors.pointSeries.thresholdLine.showLabel": "显示阈值线条", + "visTypeVislib.editors.pointSeries.thresholdLine.styleLabel": "线条样式", + "visTypeVislib.editors.pointSeries.thresholdLine.valueLabel": "阈值", + "visTypeVislib.editors.pointSeries.thresholdLine.widthLabel": "线条宽度", + "visTypeVislib.editors.pointSeries.thresholdLineSettingsTitle": "阈值线条", + "visTypeVislib.functions.pie.help": "饼图可视化", + "visTypeVislib.functions.vislib.help": "Vislib 可视化", + "visTypeVislib.gauge.alignmentAutomaticTitle": "自动", + "visTypeVislib.gauge.alignmentHorizontalTitle": "水平", + "visTypeVislib.gauge.alignmentVerticalTitle": "垂直", + "visTypeVislib.gauge.gaugeDescription": "仪表盘图指示指标的状态。用于显示指标值与参考阈值的相关程度。", + "visTypeVislib.gauge.gaugeTitle": "仪表盘图", + "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", + "visTypeVislib.gauge.gaugeTypes.circleText": "圆形", + "visTypeVislib.gauge.groupTitle": "拆分组", + "visTypeVislib.gauge.metricTitle": "指标", + "visTypeVislib.goal.goalDescription": "目标图指示与最终目标的接近程度。", + "visTypeVislib.goal.goalTitle": "目标图", + "visTypeVislib.goal.groupTitle": "拆分组", + "visTypeVislib.goal.metricTitle": "指标", + "visTypeVislib.heatmap.groupTitle": "Y 轴", + "visTypeVislib.heatmap.heatmapDescription": "为矩阵中的单元格添加阴影", + "visTypeVislib.heatmap.heatmapTitle": "热力图", + "visTypeVislib.heatmap.metricTitle": "值", + "visTypeVislib.heatmap.segmentTitle": "X 轴", + "visTypeVislib.heatmap.splitTitle": "拆分图表", + "visTypeVislib.histogram.groupTitle": "拆分序列", + "visTypeVislib.histogram.histogramDescription": "向每个轴赋予连续变量", + "visTypeVislib.histogram.histogramTitle": "垂直条形图", + "visTypeVislib.histogram.metricTitle": "Y 轴", + "visTypeVislib.histogram.radiusTitle": "点大小", + "visTypeVislib.histogram.segmentTitle": "X 轴", + "visTypeVislib.histogram.splitTitle": "拆分图表", + "visTypeVislib.horizontalBar.groupTitle": "拆分序列", + "visTypeVislib.horizontalBar.horizontalBarDescription": "向每个轴赋予连续变量", + "visTypeVislib.horizontalBar.horizontalBarTitle": "水平条形图", + "visTypeVislib.horizontalBar.metricTitle": "Y 轴", + "visTypeVislib.horizontalBar.radiusTitle": "点大小", + "visTypeVislib.horizontalBar.segmentTitle": "X 轴", + "visTypeVislib.horizontalBar.splitTitle": "拆分图表", + "visTypeVislib.interpolationModes.smoothedText": "平滑", + "visTypeVislib.interpolationModes.steppedText": "渐变", + "visTypeVislib.interpolationModes.straightText": "直线", + "visTypeVislib.legendPositions.bottomText": "下", + "visTypeVislib.legendPositions.leftText": "左", + "visTypeVislib.legendPositions.rightText": "右", + "visTypeVislib.legendPositions.topText": "上", + "visTypeVislib.line.groupTitle": "拆分序列", + "visTypeVislib.line.lineDescription": "突出趋势", + "visTypeVislib.line.lineTitle": "折线图", + "visTypeVislib.line.metricTitle": "Y 轴", + "visTypeVislib.line.radiusTitle": "点大小", + "visTypeVislib.line.segmentTitle": "X 轴", + "visTypeVislib.line.splitTitle": "拆分图表", + "visTypeVislib.pie.metricTitle": "切片大小", + "visTypeVislib.pie.pieDescription": "比较整体的各个部分", + "visTypeVislib.pie.pieTitle": "饼图", + "visTypeVislib.pie.segmentTitle": "拆分切片", + "visTypeVislib.pie.splitTitle": "拆分图表", + "visTypeVislib.scaleTypes.linearText": "线性", + "visTypeVislib.scaleTypes.logText": "对数", + "visTypeVislib.scaleTypes.squareRootText": "平方根", + "visTypeVislib.thresholdLine.style.dashedText": "虚线", + "visTypeVislib.thresholdLine.style.dotdashedText": "点虚线", + "visTypeVislib.thresholdLine.style.fullText": "实线", + "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", + "visTypeVislib.vislib.tooltip.valueLabel": "値", + "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", + "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "筛除值 {legendDataLabel}", + "visTypeVislib.vislib.legend.loadingLabel": "正在加载……", + "visTypeVislib.vislib.legend.setColorScreenReaderDescription": "为值 {legendDataLabel} 设置颜色", + "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "切换图例", + "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "切换图例", + "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}切换选项", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "退出全屏", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "在全屏模式下,按 ESC 键可退出。", "kibana-react.savedObjects.finder.filterButtonLabel": "类型", diff --git a/yarn.lock b/yarn.lock index b534a7a4fcb799..198174d0132fae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4828,6 +4828,11 @@ resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.25.tgz#b6f55062827a4787fe4ab151cf3412a468e65271" integrity sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ== +"@types/numeral@^0.0.26": + version "0.0.26" + resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.26.tgz#cfab9842ef9349ce714b06722940ca7ebf8a6298" + integrity sha512-DwCsRqeOWopdEsm5KLTxKVKDSDoj+pzZD1vlwu1GQJ6IF3RhjuleYlRwyRH6MJLGaf3v8wFTnC6wo3yYfz0bnA== + "@types/object-hash@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.0.tgz#b20db2074129f71829d61ff404e618c4ac3d73cf" From f26596145ece8e8cb8a5e7fb671bb9953f23eb52 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 21 Jan 2020 18:49:57 +0200 Subject: [PATCH 22/59] Clean up search service (#53766) * deprecate msearch * Missing export * adjust tests, revert loading method of esaggs/boot * getInjectedMetadata * Fix jest tests * update default strategy abort test * notice update * Allow running discover errors test independently * Remove batchSearches * Detect painless script error * don't show notifications for aborted requests * Fix jest tests * Restore loader indicator * Decreace loading count on error * update search test * Trigger digest after fetching fresh index patterns * Revert isEqual * accurate revert * Return full error details to client from search endpoint * Re-throw AbortError from http when user aborts request. * fix typo * typo * Adjust routes jest test * Restore msearch using a separate es connection * typescript fixes * set http service mock * Move es client to dat aplugin, for follow up PR * Add karma mock * krma mock * fix tests * ts * Pass in version dynamically * add headers to esClient host * Restored fetch soon test Use tap for loadingCount side effects * Cleanup search params * Cleanup search params test * Revert "Cleanup search params" This reverts commit ca9dea01d59fddd10b43513450707421248b96d5. * Revert "Cleanup search params test" This reverts commit 30b94786122181f41ac44606899655ed85c0dc52. * Revert code to use old es client until #44302 is resolved * Revert changes to getPainlessError * Fix jest test * Refactor esClient to trigger loadingIndicator * fixing tests * use esClient from searchService * git remove comment * fix jest Co-authored-by: Elastic Machine --- src/core/public/http/fetch.ts | 6 +- src/legacy/core_plugins/data/public/plugin.ts | 19 +++- .../build_tabular_inspector_data.ts | 4 +- .../data/public/search/expressions/esaggs.ts | 10 +- .../public/search/fetch/call_client.test.ts | 2 +- .../data/public/search/fetch/call_client.ts | 6 +- .../data/public/search/fetch/fetch_soon.ts | 10 +- .../data/public/search/fetch/types.ts | 3 +- .../search_source/search_source.test.ts | 31 +++++-- .../search/search_source/search_source.ts | 34 ++++--- .../default_search_strategy.test.ts | 44 ++++++--- .../default_search_strategy.ts | 35 ++++++- .../np_ready/angular/get_painless_error.ts | 3 +- .../visualizations/public/legacy_imports.ts | 1 - .../components/visualization_requesterror.tsx | 2 +- .../public/np_ready/public/index.ts | 2 + .../new_platform/new_platform.karma_mock.js | 8 ++ src/plugins/data/public/mocks.ts | 17 +++- src/plugins/data/public/plugin.ts | 21 ++++- .../create_app_mount_context_search.test.ts | 50 +++++----- .../search/create_app_mount_context_search.ts | 15 ++- .../public/search/es_client/get_es_client.ts | 93 +++++++++++++++++++ .../data/public/search/es_client/index.ts | 20 ++++ .../data/public/search/search_service.ts | 22 ++++- src/plugins/data/public/services.ts | 16 +++- .../query_string_input.test.tsx.snap | 72 ++++++++++++++ src/plugins/data/server/search/routes.test.ts | 15 ++- src/plugins/data/server/search/routes.ts | 10 +- test/functional/apps/discover/_errors.js | 3 +- .../maps/public/angular/map_controller.js | 4 +- 30 files changed, 474 insertions(+), 104 deletions(-) rename src/legacy/{ui/public/inspector => core_plugins/data/public/search/expressions}/build_tabular_inspector_data.ts (95%) create mode 100644 src/plugins/data/public/search/es_client/get_es_client.ts create mode 100644 src/plugins/data/public/search/es_client/index.ts diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index b86f1f5c08029b..b7ceaed6e56a76 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -133,7 +133,11 @@ export class Fetch { try { response = await window.fetch(request); } catch (err) { - throw new HttpFetchError(err.message, request); + if (err.name === 'AbortError') { + throw err; + } else { + throw new HttpFetchError(err.message, request); + } } const contentType = response.headers.get('Content-Type') || ''; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 893e477b38583d..5329702348207f 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -20,15 +20,24 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { SearchService, SearchStart } from './search'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { ExpressionsSetup } from '../../../../plugins/expressions/public'; import { setFieldFormats, setNotifications, setIndexPatterns, setQueryService, + setSearchService, + setUiSettings, + setInjectedMetadata, + setHttp, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; +export interface DataPluginSetupDependencies { + expressions: ExpressionsSetup; +} + export interface DataPluginStartDependencies { data: DataPublicPluginStart; } @@ -54,18 +63,24 @@ export interface DataStart { * or static code. */ -export class DataPlugin implements Plugin { +export class DataPlugin + implements Plugin { private readonly search = new SearchService(); - public setup(core: CoreSetup) {} + public setup(core: CoreSetup) { + setInjectedMetadata(core.injectedMetadata); + } public start(core: CoreStart, { data }: DataPluginStartDependencies): DataStart { // This is required for when Angular code uses Field and FieldList. setFieldFormats(data.fieldFormats); setQueryService(data.query); + setSearchService(data.search); setIndexPatterns(data.indexPatterns); setFieldFormats(data.fieldFormats); setNotifications(core.notifications); + setUiSettings(core.uiSettings); + setHttp(core.http); return { search: this.search.start(core), diff --git a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts similarity index 95% rename from src/legacy/ui/public/inspector/build_tabular_inspector_data.ts rename to src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts index b09ed60e7186f9..6e6d2a15fa2ac8 100644 --- a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts @@ -19,8 +19,8 @@ import { set } from 'lodash'; // @ts-ignore -import { createFilter } from '../../../core_plugins/visualizations/public'; -import { FormattedData } from './adapters'; +import { createFilter } from '../../../../visualizations/public'; +import { FormattedData } from '../../../../../../plugins/inspector/public'; interface Column { id: string; diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 4ec4dbd7f88d69..889c747c9a62e3 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -34,14 +34,8 @@ import { getTime, FilterManager, } from '../../../../../../plugins/data/public'; -import { - SearchSource, - ISearchSource, - getRequestInspectorStats, - getResponseInspectorStats, -} from '../../../../../ui/public/courier'; -import { buildTabularInspectorData } from '../../../../../ui/public/inspector/build_tabular_inspector_data'; +import { buildTabularInspectorData } from './build_tabular_inspector_data'; import { calculateObjectHash } from '../../../../visualizations/public'; // @ts-ignore import { tabifyAggResponse } from '../../../../../ui/public/agg_response/tabify/tabify'; @@ -49,6 +43,8 @@ import { PersistedState } from '../../../../../ui/public/persisted_state'; import { Adapters } from '../../../../../../plugins/inspector/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getQueryService, getIndexPatterns } from '../../../../../../plugins/data/public/services'; +import { ISearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../..'; +import { SearchSource } from '../search_source'; export interface RequestHandlerParams { searchSource: ISearchSource; diff --git a/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts b/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts index 74c87d77dd4fd0..24a36c9db9df76 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts @@ -76,7 +76,7 @@ describe('callClient', () => { test('Passes the additional arguments it is given to the search strategy', () => { const searchRequests = [{ _searchStrategyId: 0 }]; - const args = { es: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; + const args = { searchService: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; callClient(searchRequests, [], args); diff --git a/src/legacy/core_plugins/data/public/search/fetch/call_client.ts b/src/legacy/core_plugins/data/public/search/fetch/call_client.ts index 43da27f941e4e2..ad18775d5f1448 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/call_client.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/call_client.ts @@ -26,7 +26,7 @@ import { SearchRequest } from '../types'; export function callClient( searchRequests: SearchRequest[], requestsOptions: FetchOptions[] = [], - { es, config, esShardTimeout }: FetchHandlers + fetchHandlers: FetchHandlers ) { // Correlate the options with the request that they're associated with const requestOptionEntries: Array<[ @@ -53,9 +53,7 @@ export function callClient( // then an error would have been thrown above const { searching, abort } = searchStrategy!.search({ searchRequests: requests, - es, - config, - esShardTimeout, + ...fetchHandlers, }); requests.forEach((request, i) => { diff --git a/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts b/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts index 75de85e02a1a2b..4830464047ad66 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts @@ -28,10 +28,10 @@ import { SearchRequest, SearchResponse } from '../types'; export async function fetchSoon( request: SearchRequest, options: FetchOptions, - { es, config, esShardTimeout }: FetchHandlers + fetchHandlers: FetchHandlers ) { - const msToDelay = config.get('courier:batchSearches') ? 50 : 0; - return delayedFetch(request, options, { es, config, esShardTimeout }, msToDelay); + const msToDelay = fetchHandlers.config.get('courier:batchSearches') ? 50 : 0; + return delayedFetch(request, options, fetchHandlers, msToDelay); } /** @@ -64,7 +64,7 @@ let fetchInProgress: Promise | null = null; async function delayedFetch( request: SearchRequest, options: FetchOptions, - { es, config, esShardTimeout }: FetchHandlers, + fetchHandlers: FetchHandlers, ms: number ) { const i = requestsToFetch.length; @@ -73,7 +73,7 @@ async function delayedFetch( const responses = await (fetchInProgress = fetchInProgress || delay(() => { - const response = callClient(requestsToFetch, requestOptions, { es, config, esShardTimeout }); + const response = callClient(requestsToFetch, requestOptions, fetchHandlers); requestsToFetch = []; requestOptions = []; fetchInProgress = null; diff --git a/src/legacy/core_plugins/data/public/search/fetch/types.ts b/src/legacy/core_plugins/data/public/search/fetch/types.ts index 0887a1f84c7c8c..fba14119d83c37 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/types.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/types.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ISearchStart } from 'src/plugins/data/public'; import { IUiSettingsClient } from '../../../../../../core/public'; import { SearchRequest, SearchResponse } from '../types'; @@ -35,7 +36,7 @@ export interface FetchOptions { } export interface FetchHandlers { - es: ApiCaller; + searchService: ISearchStart; config: IUiSettingsClient; esShardTimeout: number; } diff --git a/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts b/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts index 28f8dba9a75de3..ebeee60b67c8ad 100644 --- a/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts @@ -19,19 +19,34 @@ import { SearchSource } from '../search_source'; import { IndexPattern } from '../../../../../../plugins/data/public'; - -jest.mock('ui/new_platform'); +import { + setSearchService, + setUiSettings, + setInjectedMetadata, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/services'; + +import { + injectedMetadataServiceMock, + uiSettingsServiceMock, +} from '../../../../../../core/public/mocks'; + +setUiSettings(uiSettingsServiceMock.createStartContract()); +setInjectedMetadata(injectedMetadataServiceMock.createSetupContract()); +setSearchService({ + search: jest.fn(), + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, +}); jest.mock('../fetch', () => ({ fetchSoon: jest.fn().mockResolvedValue({}), })); -jest.mock('ui/chrome', () => ({ - dangerouslyGetActiveInjector: () => ({ - get: jest.fn(), - }), -})); - const getComputedFields = () => ({ storedFields: [], scriptFields: [], diff --git a/src/legacy/core_plugins/data/public/search/search_source/search_source.ts b/src/legacy/core_plugins/data/public/search/search_source/search_source.ts index 6efcae4d4b88dc..e977db713ebaa8 100644 --- a/src/legacy/core_plugins/data/public/search/search_source/search_source.ts +++ b/src/legacy/core_plugins/data/public/search/search_source/search_source.ts @@ -70,8 +70,6 @@ */ import _ from 'lodash'; -import { npSetup } from 'ui/new_platform'; -import chrome from 'ui/chrome'; import { normalizeSortRequest } from './normalize_sort_request'; import { fetchSoon } from '../fetch'; import { fieldWildcardFilter } from '../../../../../../plugins/kibana_utils/public'; @@ -79,10 +77,14 @@ import { getHighlightRequest, esFilters, esQuery } from '../../../../../../plugi import { RequestFailure } from '../fetch/errors'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { SearchSourceOptions, SearchSourceFields, SearchRequest } from './types'; -import { FetchOptions, ApiCaller } from '../fetch/types'; +import { FetchOptions } from '../fetch/types'; -const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout') as number; -const config = npSetup.core.uiSettings; +import { + getSearchService, + getUiSettings, + getInjectedMetadata, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/services'; export type ISearchSource = Pick; @@ -192,21 +194,23 @@ export class SearchSource { * @async */ async fetch(options: FetchOptions = {}) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const es = $injector.get('es') as ApiCaller; - await this.requestIsStarting(options); const searchRequest = await this.flatten(); this.history = [searchRequest]; + const esShardTimeout = getInjectedMetadata().getInjectedVar('esShardTimeout') as number; const response = await fetchSoon( searchRequest, { ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), ...options, }, - { es, config, esShardTimeout } + { + searchService: getSearchService(), + config: getUiSettings(), + esShardTimeout, + } ); if (response.error) { @@ -313,7 +317,11 @@ export class SearchSource { case 'source': return addToBody('_source', val); case 'sort': - const sort = normalizeSortRequest(val, this.getField('index'), config.get('sort:options')); + const sort = normalizeSortRequest( + val, + this.getField('index'), + getUiSettings().get('sort:options') + ); return addToBody(key, sort); default: return addToBody(key, val); @@ -359,7 +367,7 @@ export class SearchSource { if (body._source) { // exclude source fields for this index pattern specified by the user - const filter = fieldWildcardFilter(body._source.excludes, config.get('metaFields')); + const filter = fieldWildcardFilter(body._source.excludes, getUiSettings().get('metaFields')); body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => filter(docvalueField.field) ); @@ -377,11 +385,11 @@ export class SearchSource { _.set(body, '_source.includes', remainingFields); } - const esQueryConfigs = esQuery.getEsQueryConfig(config); + const esQueryConfigs = esQuery.getEsQueryConfig(getUiSettings()); body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = getHighlightRequest(body.query, config.get('doc_table:highlight')); + body.highlight = getHighlightRequest(body.query, getUiSettings().get('doc_table:highlight')); delete searchRequest.highlightAll; } diff --git a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts index 0ec6a6c2e143e7..8caf20c50cd3aa 100644 --- a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts @@ -37,9 +37,16 @@ const searchMockResponse: any = Promise.resolve([]); searchMockResponse.abort = jest.fn(); const searchMock = jest.fn().mockReturnValue(searchMockResponse); +const newSearchMockResponse: any = Promise.resolve([]); +newSearchMockResponse.abort = jest.fn(); +const newSearchMock = jest.fn().mockReturnValue({ + toPromise: () => searchMockResponse, +}); + describe('defaultSearchStrategy', function() { describe('search', function() { let searchArgs: MockedKeys>; + let es: any; beforeEach(() => { msearchMockResponse.abort.mockClear(); @@ -55,17 +62,24 @@ describe('defaultSearchStrategy', function() { }, ], esShardTimeout: 0, - es: { - msearch: msearchMock, - search: searchMock, + searchService: { + search: newSearchMock, + __LEGACY: { + esClient: { + search: searchMock, + msearch: msearchMock, + }, + }, }, }; + + es = searchArgs.searchService.__LEGACY.esClient; }); test('does not send max_concurrent_shard_requests by default', async () => { const config = getConfigStub({ 'courier:batchSearches': true }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); + expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); }); test('allows configuration of max_concurrent_shard_requests', async () => { @@ -74,13 +88,13 @@ describe('defaultSearchStrategy', function() { 'courier:maxConcurrentShardRequests': 42, }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); + expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); }); test('should set rest_total_hits_as_int to true on a request', async () => { const config = getConfigStub({ 'courier:batchSearches': true }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); + expect(es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); }); test('should set ignore_throttled=false when including frozen indices', async () => { @@ -89,7 +103,7 @@ describe('defaultSearchStrategy', function() { 'search:includeFrozen': true, }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); + expect(es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); }); test('should properly call abort with msearch', () => { @@ -100,12 +114,18 @@ describe('defaultSearchStrategy', function() { expect(msearchMockResponse.abort).toHaveBeenCalled(); }); - test('should properly abort with search', async () => { - const config = getConfigStub({ - 'courier:batchSearches': false, - }); + test('should call new search service', () => { + const config = getConfigStub(); + search({ ...searchArgs, config }); + expect(searchMock).toHaveBeenCalled(); + expect(newSearchMock).toHaveBeenCalledTimes(0); + }); + + test('should properly abort with new search service', async () => { + const abortSpy = jest.spyOn(AbortController.prototype, 'abort'); + const config = getConfigStub({}); search({ ...searchArgs, config }).abort(); - expect(searchMockResponse.abort).toHaveBeenCalled(); + expect(abortSpy).toHaveBeenCalled(); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts index 9bfa1df71aa81a..39789504de0a74 100644 --- a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts +++ b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts @@ -38,7 +38,14 @@ export const defaultSearchStrategy: SearchStrategyProvider = { }, }; -function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) { +// @deprecated +function msearch({ + searchRequests, + searchService, + config, + esShardTimeout, +}: SearchStrategySearchParams) { + const es = searchService.__LEGACY.esClient; const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => { const inlineHeader = { index: index.title || index, @@ -57,19 +64,39 @@ function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategyS ...getMSearchParams(config), body: `${inlineRequests.join('\n')}\n`, }); + return { - searching: searching.then(({ responses }) => responses), + searching: searching.then(({ responses }: any) => responses), abort: searching.abort, }; } -function search({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) { +function search({ + searchRequests, + searchService, + config, + esShardTimeout, +}: SearchStrategySearchParams) { const abortController = new AbortController(); const searchParams = getSearchParams(config, esShardTimeout); + const es = searchService.__LEGACY.esClient; const promises = searchRequests.map(({ index, body }) => { const searching = es.search({ index: index.title || index, body, ...searchParams }); abortController.signal.addEventListener('abort', searching.abort); - return searching.catch(({ response }) => JSON.parse(response)); + return searching.catch(({ response }: any) => JSON.parse(response)); + /* + * Once #44302 is resolved, replace the old implementation with this one - + * const params = { + * index: index.title || index, + * body, + * ...searchParams, + * }; + * const { signal } = abortController; + * return searchService + * .search({ params }, { signal }) + * .toPromise() + * .then(({ rawResponse }) => rawResponse); + */ }); return { searching: Promise.all(promises), diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts index 212fd870a5aeb8..2bbeea9d675c7e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts @@ -25,6 +25,7 @@ export function getPainlessError(error: Error) { error, 'resp.error.root_cause' ); + const message: string = get(error, 'message'); if (!rootCause) { return; @@ -43,6 +44,6 @@ export function getPainlessError(error: Error) { defaultMessage: "Error with Painless scripted field '{script}'.", values: { script }, }), - error: error.message, + error: message, }; } diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index b750557c24b94a..fd40c831ce0efa 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,7 +18,6 @@ */ export { PersistedState } from '../../../ui/public/persisted_state'; -export { SearchError } from '../../../ui/public/courier/search_strategy/search_error'; export { AggConfig } from '../../../ui/public/agg_types/agg_config'; export { AggConfigs } from '../../../ui/public/agg_types/agg_configs'; export { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx index 8b9fded919f135..1af9aa3c3e6021 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx @@ -19,7 +19,7 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import { SearchError } from '../../../legacy_imports'; +import { SearchError } from '../../../../../data/public/search/search_strategy'; interface VisualizationRequestErrorProps { onInit?: () => void; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index 2e9d055858a483..29ff812b95473f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -52,3 +52,5 @@ export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/bui // @ts-ignore export { updateOldState } from './legacy/vis_update_state'; export { calculateObjectHash } from './legacy/calculate_object_hash'; +// @ts-ignore +export { createFiltersFromEvent } from './filters/vis_filters'; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index d3f74a540b9604..dfd26bc4be0397 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -236,6 +236,14 @@ export const npStart = { history: sinon.fake(), }, }, + search: { + __LEGACY: { + esClient: { + search: sinon.fake(), + msearch: sinon.fake(), + }, + }, + }, fieldFormats: getFieldFormatsRegistry(mockCore), }, share: { diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 08a7a3ef11537b..f44d40f533eed3 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -69,13 +69,28 @@ const createStartContract = (): Start => { const startContract = { autocomplete: autocompleteMock, getSuggestions: jest.fn(), - search: { search: jest.fn() }, + search: { + search: jest.fn(), + + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, + }, fieldFormats: fieldFormatsMock as FieldFormatsStart, query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), SearchBar: jest.fn(), }, + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, indexPatterns: {} as IndexPatternsContract, }; return startContract; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 78abcbcfec467f..ce8ca0317bd7d8 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -17,7 +17,13 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + PackageInfo, +} from 'src/core/public'; import { Storage, IStorageWrapper } from '../../kibana_utils/public'; import { DataPublicPluginSetup, @@ -31,7 +37,13 @@ import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; import { IndexPatterns } from './index_patterns'; -import { setNotifications, setFieldFormats, setOverlays, setIndexPatterns } from './services'; +import { + setNotifications, + setFieldFormats, + setOverlays, + setIndexPatterns, + setHttp, +} from './services'; import { createFilterAction, GLOBAL_APPLY_FILTER_ACTION } from './actions'; import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; import { createSearchBar } from './ui/search_bar/create_search_bar'; @@ -42,12 +54,14 @@ export class DataPublicPlugin implements Plugin { it('Returns search fn when there are no strategies', () => { - const context = createAppMountSearchContext({}); + const context = createAppMountSearchContext({}, new BehaviorSubject(0)); expect(context.search).toBeDefined(); }); it(`Search throws an error when the strategy doesn't exist`, () => { - const context = createAppMountSearchContext({}); + const context = createAppMountSearchContext({}, new BehaviorSubject(0)); expect(() => context.search({}, {}, 'noexist').toPromise()).toThrowErrorMatchingInlineSnapshot( `"Strategy with name noexist does not exist"` ); }); it(`Search fn is called on appropriate strategy name`, done => { - const context = createAppMountSearchContext({ - mysearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 98 })), - }), - anothersearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 0 })), - }), - }); + const context = createAppMountSearchContext( + { + mysearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 98 })), + }), + anothersearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 0 })), + }), + }, + new BehaviorSubject(0) + ); context.search({}, {}, 'mysearch').subscribe(response => { expect(response).toEqual({ percentComplete: 98 }); @@ -52,16 +55,19 @@ describe('Create app mount search context', () => { }); it(`Search fn is called with the passed in request object`, done => { - const context = createAppMountSearchContext({ - mysearch: search => { - return Promise.resolve({ - search: request => { - expect(request).toEqual({ greeting: 'hi' }); - return from(Promise.resolve({})); - }, - }); + const context = createAppMountSearchContext( + { + mysearch: search => { + return Promise.resolve({ + search: request => { + expect(request).toEqual({ greeting: 'hi' }); + return from(Promise.resolve({})); + }, + }); + }, }, - }); + new BehaviorSubject(0) + ); context.search({ greeting: 'hi' } as any, {}, 'mysearch').subscribe( response => {}, () => {}, diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index 5659a9c863dc18..f480b8f3e042e6 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -17,8 +17,8 @@ * under the License. */ -import { mergeMap } from 'rxjs/operators'; -import { from } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; +import { from, BehaviorSubject } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -30,7 +30,8 @@ import { TStrategyTypes } from './strategy_types'; import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; export const createAppMountSearchContext = ( - searchStrategies: TSearchStrategiesMap + searchStrategies: TSearchStrategiesMap, + loadingCount$: BehaviorSubject ): ISearchAppMountContext => { const getSearchStrategy = ( strategyName?: K @@ -48,7 +49,13 @@ export const createAppMountSearchContext = ( const strategyPromise = getSearchStrategy(strategyName); return from(strategyPromise).pipe( mergeMap(strategy => { - return strategy.search(request, options); + loadingCount$.next(loadingCount$.getValue() + 1); + return strategy.search(request, options).pipe( + tap( + error => loadingCount$.next(loadingCount$.getValue() - 1), + complete => loadingCount$.next(loadingCount$.getValue() - 1) + ) + ); }) ); }; diff --git a/src/plugins/data/public/search/es_client/get_es_client.ts b/src/plugins/data/public/search/es_client/get_es_client.ts new file mode 100644 index 00000000000000..6c271643ba0124 --- /dev/null +++ b/src/plugins/data/public/search/es_client/get_es_client.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +import { default as es } from 'elasticsearch-browser/elasticsearch'; +import { CoreStart, PackageInfo } from 'kibana/public'; +import { BehaviorSubject } from 'rxjs'; + +export function getEsClient( + injectedMetadata: CoreStart['injectedMetadata'], + http: CoreStart['http'], + packageInfo: PackageInfo, + loadingCount$: BehaviorSubject +) { + const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; + const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; + + // Use legacy es client for msearch. + const client = es.Client({ + host: getEsUrl(http, packageInfo), + log: 'info', + requestTimeout: esRequestTimeout, + apiVersion: esApiVersion, + }); + + return { + search: wrapEsClientMethod(client, 'search', loadingCount$), + msearch: wrapEsClientMethod(client, 'msearch', loadingCount$), + create: wrapEsClientMethod(client, 'create', loadingCount$), + }; +} + +function wrapEsClientMethod(esClient: any, method: string, loadingCount$: BehaviorSubject) { + return (args: any) => { + // esClient returns a promise, with an additional abort handler + // To tap into the abort handling, we have to override that abort handler. + const customPromiseThingy = esClient[method](args); + const { abort } = customPromiseThingy; + let resolved = false; + + // Start LoadingIndicator + loadingCount$.next(loadingCount$.getValue() + 1); + + // Stop LoadingIndicator when user aborts + customPromiseThingy.abort = () => { + abort(); + if (!resolved) { + resolved = true; + loadingCount$.next(loadingCount$.getValue() - 1); + } + }; + + // Stop LoadingIndicator when promise finishes + customPromiseThingy.finally(() => { + resolved = true; + loadingCount$.next(loadingCount$.getValue() - 1); + }); + + return customPromiseThingy; + }; +} + +function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) { + const a = document.createElement('a'); + a.href = http.basePath.prepend('/elasticsearch'); + const protocolPort = /https/.test(a.protocol) ? 443 : 80; + const port = a.port || protocolPort; + return { + host: a.hostname, + port, + protocol: a.protocol, + pathname: a.pathname, + headers: { + 'kbn-version': packageInfo.version, + }, + }; +} diff --git a/src/plugins/data/public/search/es_client/index.ts b/src/plugins/data/public/search/es_client/index.ts new file mode 100644 index 00000000000000..bf1a3f5d6e7c4f --- /dev/null +++ b/src/plugins/data/public/search/es_client/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { getEsClient } from './get_es_client'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 6030884c9f6b1d..6f3e228939d6dd 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { BehaviorSubject } from 'rxjs'; import { Plugin, CoreSetup, @@ -23,6 +24,7 @@ import { CoreStart, IContextContainer, PluginOpaqueId, + PackageInfo, } from '../../../../core/public'; import { ISearchAppMountContext } from './i_search_app_mount_context'; @@ -37,6 +39,7 @@ import { import { TStrategyTypes } from './strategy_types'; import { esSearchService } from './es_search'; import { ISearchGeneric } from './i_search'; +import { getEsClient } from './es_client'; /** * Extends the AppMountContext so other plugins have access @@ -50,6 +53,9 @@ declare module 'kibana/public' { export interface ISearchStart { search: ISearchGeneric; + __LEGACY: { + esClient: any; + }; } /** @@ -74,11 +80,16 @@ export class SearchService implements Plugin { private contextContainer?: IContextContainer>; private search?: ISearchGeneric; + private readonly loadingCount$ = new BehaviorSubject(0); constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): ISearchSetup { - const search = (this.search = createAppMountSearchContext(this.searchStrategies).search); + core.http.addLoadingCountSource(this.loadingCount$); + const search = (this.search = createAppMountSearchContext( + this.searchStrategies, + this.loadingCount$ + ).search); core.application.registerMountContext<'search'>('search', () => { return { search }; }); @@ -115,11 +126,16 @@ export class SearchService implements Plugin { return api; } - public start(core: CoreStart) { + public start(core: CoreStart, packageInfo: PackageInfo) { if (!this.search) { throw new Error('Search should always be defined'); } - return { search: this.search }; + return { + search: this.search, + __LEGACY: { + esClient: getEsClient(core.injectedMetadata, core.http, packageInfo, this.loadingCount$), + }, + }; } public stop() {} diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 76b3283220f673..6a15893f573d83 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -18,7 +18,7 @@ */ import { NotificationsStart } from 'src/core/public'; -import { CoreStart } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import { FieldFormatsStart } from '.'; import { createGetterSetter } from '../../kibana_utils/public'; import { IndexPatternsContract } from './index_patterns'; @@ -28,6 +28,12 @@ export const [getNotifications, setNotifications] = createGetterSetter( + 'UiSettings' +); + +export const [getHttp, setHttp] = createGetterSetter('Http'); + export const [getFieldFormats, setFieldFormats] = createGetterSetter( 'FieldFormats' ); @@ -41,3 +47,11 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('Query'); + +export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter< + CoreSetup['injectedMetadata'] +>('InjectedMetadata'); + +export const [getSearchService, setSearchService] = createGetterSetter< + DataPublicPluginStart['search'] +>('Search'); diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index d8db53d4c6020b..f38adff8920995 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -150,6 +150,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -205,6 +211,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -780,6 +792,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -835,6 +853,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -1392,6 +1416,12 @@ exports[`QueryStringInput Should pass the query language to the language switche "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -1447,6 +1477,12 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -2019,6 +2055,12 @@ exports[`QueryStringInput Should pass the query language to the language switche "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -2074,6 +2116,12 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -2631,6 +2679,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -2686,6 +2740,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -3258,6 +3318,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -3313,6 +3379,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index a2394d88f39314..6ea0799f790fcf 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -65,8 +65,13 @@ describe('Search service', () => { expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' }); }); - it('handler throws internal error if the search throws an error', async () => { - const mockSearch = jest.fn().mockRejectedValue('oh no'); + it('handler throws an error if the search throws an error', async () => { + const mockSearch = jest.fn().mockRejectedValue({ + message: 'oh no', + body: { + error: 'oops', + }, + }); const mockContext = { core: { elasticsearch: { @@ -93,7 +98,9 @@ describe('Search service', () => { expect(mockSearch).toBeCalled(); expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy); - expect(mockResponse.internalError).toBeCalled(); - expect(mockResponse.internalError.mock.calls[0][0]).toEqual({ body: 'oh no' }); + expect(mockResponse.customError).toBeCalled(); + const error: any = mockResponse.customError.mock.calls[0][0]; + expect(error.body.message).toBe('oh no'); + expect(error.body.attributes.error).toBe('oops'); }); }); diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index eaa72548e08ee7..6f726771c41b2f 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -39,7 +39,15 @@ export function registerSearchRoute(router: IRouter): void { const response = await context.search!.search(searchRequest, {}, strategy); return res.ok({ body: response }); } catch (err) { - return res.internalError({ body: err }); + return res.customError({ + statusCode: err.statusCode, + body: { + message: err.message, + attributes: { + error: err.body.error, + }, + }, + }); } } ); diff --git a/test/functional/apps/discover/_errors.js b/test/functional/apps/discover/_errors.js index 53dcd8cc9e5c14..7dbb93c884f46d 100644 --- a/test/functional/apps/discover/_errors.js +++ b/test/functional/apps/discover/_errors.js @@ -22,10 +22,11 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'discover']); describe('errors', function describeIndexTests() { before(async function() { + await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('invalid_scripted_field'); await PageObjects.common.navigateToApp('discover'); }); diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index 75971d5dfe2a84..eec97dc5c71e9c 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -367,7 +367,9 @@ app.controller( if (prevIndexPatternIds !== nextIndexPatternIds) { return; } - $scope.indexPatterns = indexPatterns; + $scope.$evalAsync(() => { + $scope.indexPatterns = indexPatterns; + }); } $scope.isFullScreen = false; From 5aa85dc7516450a8bf5fe5c3a79f51742fb0f5d4 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Tue, 21 Jan 2020 12:35:02 -0500 Subject: [PATCH 23/59] Update Monitoring plugin's Elasticsearch configuration (#55119) * Fix Monitoring plugin Elasticsearch SSL config Plugin now allows "keystore" and "truststore" values in its config schema as the documentation currently states. Plugin also now reads PEM and PKCS12 files off of the filesystem before attempting to create an Elasticsearch client. * Add missing Elasticsearch config deprecations Several Elasticsearch config deprecations were overlooked for monitoring-specific Elasticsearch settings. --- .../monitoring/__tests__/deprecations.js | 50 +++++ x-pack/legacy/plugins/monitoring/config.js | 8 + .../legacy/plugins/monitoring/deprecations.js | 26 +++ .../es_client/__tests__/instantiate_client.js | 15 +- .../server/es_client/instantiate_client.js | 13 +- .../parse_elasticsearch_config.test.mocks.ts | 15 ++ .../parse_elasticsearch_config.test.ts | 181 ++++++++++++++++++ .../es_client/parse_elasticsearch_config.ts | 111 +++++++++++ .../plugins/monitoring/server/plugin.js | 9 +- 9 files changed, 408 insertions(+), 20 deletions(-) create mode 100644 x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts diff --git a/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js b/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js index aaaf3d6ad40cf1..3df93bdb24f328 100644 --- a/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js +++ b/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js @@ -80,4 +80,54 @@ describe('monitoring plugin deprecations', function() { expect(log.called).to.be(true); }); }); + + describe('elasticsearch.username', function() { + it('logs a warning if elasticsearch.username is set to "elastic"', () => { + const settings = { elasticsearch: { username: 'elastic' } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('does not log a warning if elasticsearch.username is set to something besides "elastic"', () => { + const settings = { elasticsearch: { username: 'otheruser' } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it('does not log a warning if elasticsearch.username is unset', () => { + const settings = { elasticsearch: { username: undefined } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it('logs a warning if ssl.key is set and ssl.certificate is not', () => { + const settings = { elasticsearch: { ssl: { key: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('logs a warning if ssl.certificate is set and ssl.key is not', () => { + const settings = { elasticsearch: { ssl: { certificate: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('does not log a warning if both ssl.key and ssl.certificate are set', () => { + const settings = { elasticsearch: { ssl: { key: '', certificate: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + }); }); diff --git a/x-pack/legacy/plugins/monitoring/config.js b/x-pack/legacy/plugins/monitoring/config.js index c33b0b28e830ad..91c1ee99a0b2e4 100644 --- a/x-pack/legacy/plugins/monitoring/config.js +++ b/x-pack/legacy/plugins/monitoring/config.js @@ -83,6 +83,14 @@ export const config = Joi => { certificate: Joi.string(), key: Joi.string(), keyPassphrase: Joi.string(), + keystore: Joi.object({ + path: Joi.string(), + password: Joi.string(), + }).default(), + truststore: Joi.object({ + path: Joi.string(), + password: Joi.string(), + }).default(), alwaysPresentCertificate: Joi.boolean().default(false), }).default(), apiVersion: Joi.string().default('master'), diff --git a/x-pack/legacy/plugins/monitoring/deprecations.js b/x-pack/legacy/plugins/monitoring/deprecations.js index 13a6a58fa8752f..c3b2b70690f33b 100644 --- a/x-pack/legacy/plugins/monitoring/deprecations.js +++ b/x-pack/legacy/plugins/monitoring/deprecations.js @@ -27,5 +27,31 @@ export const deprecations = () => { ); } }, + (settings, log) => { + const fromPath = 'xpack.monitoring.elasticsearch'; + const es = get(settings, 'elasticsearch'); + if (es) { + if (es.username === 'elastic') { + log( + `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana" user instead.` + ); + } + } + }, + (settings, log) => { + const fromPath = 'xpack.monitoring.elasticsearch.ssl'; + const ssl = get(settings, 'elasticsearch.ssl'); + if (ssl) { + if (ssl.key !== undefined && ssl.certificate === undefined) { + log( + `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } else if (ssl.certificate !== undefined && ssl.key === undefined) { + log( + `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } + } + }, ]; }; diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js b/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js index 8797f92e489bb1..6844bd5febf8ee 100644 --- a/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js +++ b/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { get, noop } from 'lodash'; +import { noop } from 'lodash'; import { exposeClient, hasMonitoringCluster } from '../instantiate_client'; function getMockServerFromConnectionUrl(monitoringClusterUrl) { @@ -26,15 +26,8 @@ function getMockServerFromConnectionUrl(monitoringClusterUrl) { }, }; - const config = { - get: path => { - return get(server, path); - }, - set: noop, - }; - return { - config, + elasticsearchConfig: server.xpack.monitoring.elasticsearch, elasticsearchPlugin: { getCluster: sinon .stub() @@ -141,12 +134,12 @@ describe('Instantiate Client', () => { describe('hasMonitoringCluster', () => { it('returns true if monitoring is configured', () => { const server = getMockServerFromConnectionUrl('http://monitoring-cluster.test:9200'); // pass null for URL to create the client using prod config - expect(hasMonitoringCluster(server.config)).to.be(true); + expect(hasMonitoringCluster(server.elasticsearchConfig)).to.be(true); }); it('returns false if monitoring is not configured', () => { const server = getMockServerFromConnectionUrl(null); - expect(hasMonitoringCluster(server.config)).to.be(false); + expect(hasMonitoringCluster(server.elasticsearchConfig)).to.be(false); }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js b/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js index 87a2e5349cf1b7..9aed1ac1456174 100644 --- a/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js +++ b/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js @@ -14,24 +14,21 @@ import { LOGGING_TAG } from '../../common/constants'; * Kibana itself is connected to a production cluster. */ -export function exposeClient({ config, events, log, elasticsearchPlugin }) { - const elasticsearchConfig = hasMonitoringCluster(config) - ? config.get('xpack.monitoring.elasticsearch') - : {}; +export function exposeClient({ elasticsearchConfig, events, log, elasticsearchPlugin }) { + const isMonitoringCluster = hasMonitoringCluster(elasticsearchConfig); const cluster = elasticsearchPlugin.createCluster('monitoring', { - ...elasticsearchConfig, + ...(isMonitoringCluster ? elasticsearchConfig : {}), plugins: [monitoringBulk], logQueries: Boolean(elasticsearchConfig.logQueries), }); events.on('stop', bindKey(cluster, 'close')); - const configSource = hasMonitoringCluster(config) ? 'monitoring' : 'production'; + const configSource = isMonitoringCluster ? 'monitoring' : 'production'; log([LOGGING_TAG, 'es-client'], `config sourced from: ${configSource} cluster`); } export function hasMonitoringCluster(config) { - const hosts = config.get('xpack.monitoring.elasticsearch.hosts'); - return Boolean(hosts && hosts.length); + return Boolean(config.hosts && config.hosts.length); } export const instantiateClient = once(exposeClient); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts new file mode 100644 index 00000000000000..42141313ceea2a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockReadFileSync = jest.fn(); +jest.mock('fs', () => ({ readFileSync: mockReadFileSync })); + +export const mockReadPkcs12Keystore = jest.fn(); +export const mockReadPkcs12Truststore = jest.fn(); +jest.mock('../../../../../../src/core/utils', () => ({ + readPkcs12Keystore: mockReadPkcs12Keystore, + readPkcs12Truststore: mockReadPkcs12Truststore, +})); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts new file mode 100644 index 00000000000000..c6f4e0fa685045 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + mockReadFileSync, + mockReadPkcs12Keystore, + mockReadPkcs12Truststore, +} from './parse_elasticsearch_config.test.mocks'; + +import { parseElasticsearchConfig } from './parse_elasticsearch_config'; + +const parse = (config: any) => { + return parseElasticsearchConfig({ + get: () => config, + }); +}; + +describe('reads files', () => { + beforeEach(() => { + mockReadFileSync.mockReset(); + mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); + mockReadPkcs12Keystore.mockReset(); + mockReadPkcs12Keystore.mockImplementation((path: string) => ({ + key: `content-of-${path}.key`, + cert: `content-of-${path}.cert`, + ca: [`content-of-${path}.ca`], + })); + mockReadPkcs12Truststore.mockReset(); + mockReadPkcs12Truststore.mockImplementation((path: string) => [`content-of-${path}`]); + }); + + it('reads certificate authorities when ssl.keystore.path is specified', () => { + const configValue = parse({ ssl: { keystore: { path: 'some-path' } } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path.ca']); + }); + + it('reads certificate authorities when ssl.truststore.path is specified', () => { + const configValue = parse({ ssl: { truststore: { path: 'some-path' } } }); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + }); + + it('reads certificate authorities when ssl.certificateAuthorities is specified', () => { + let configValue = parse({ ssl: { certificateAuthorities: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = parse({ ssl: { certificateAuthorities: ['some-path'] } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = parse({ ssl: { certificateAuthorities: ['some-path', 'another-path'] } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(2); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path', + 'content-of-another-path', + ]); + }); + + it('reads certificate authorities when ssl.keystore.path, ssl.truststore.path, and ssl.certificateAuthorities are specified', () => { + const configValue = parse({ + ssl: { + keystore: { path: 'some-path' }, + truststore: { path: 'another-path' }, + certificateAuthorities: 'yet-another-path', + }, + }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path.ca', + 'content-of-another-path', + 'content-of-yet-another-path', + ]); + }); + + it('reads a private key and certificate when ssl.keystore.path is specified', () => { + const configValue = parse({ ssl: { keystore: { path: 'some-path' } } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path.key'); + expect(configValue.ssl.certificate).toEqual('content-of-some-path.cert'); + }); + + it('reads a private key when ssl.key is specified', () => { + const configValue = parse({ ssl: { key: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path'); + }); + + it('reads a certificate when ssl.certificate is specified', () => { + const configValue = parse({ ssl: { certificate: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificate).toEqual('content-of-some-path'); + }); +}); + +describe('throws when config is invalid', () => { + beforeAll(() => { + const realFs = jest.requireActual('fs'); + mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); + const utils = jest.requireActual('../../../../../../src/core/utils'); + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Keystore(path, password) + ); + mockReadPkcs12Truststore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Truststore(path, password) + ); + }); + + it('throws if key is invalid', () => { + const value = { ssl: { key: '/invalid/key' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/key'"` + ); + }); + + it('throws if certificate is invalid', () => { + const value = { ssl: { certificate: '/invalid/cert' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/cert'"` + ); + }); + + it('throws if certificateAuthorities is invalid', () => { + const value = { ssl: { certificateAuthorities: '/invalid/ca' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/ca'"` + ); + }); + + it('throws if keystore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/keystore' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/keystore'"` + ); + }); + + it('throws if keystore does not contain a key', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({}); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"Did not find key in Elasticsearch keystore."` + ); + }); + + it('throws if keystore does not contain a certificate', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({ key: 'foo' }); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"Did not find certificate in Elasticsearch keystore."` + ); + }); + + it('throws if truststore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/truststore' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/truststore'"` + ); + }); + + it('throws if key and keystore.path are both specified', () => { + const value = { ssl: { key: 'foo', keystore: { path: 'bar' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"[config validation of [xpack.monitoring.elasticsearch].ssl]: cannot use [key] when [keystore.path] is specified"` + ); + }); + + it('throws if certificate and keystore.path are both specified', () => { + const value = { ssl: { certificate: 'foo', keystore: { path: 'bar' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"[config validation of [xpack.monitoring.elasticsearch].ssl]: cannot use [certificate] when [keystore.path] is specified"` + ); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts new file mode 100644 index 00000000000000..70e6235602b5b9 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { readFileSync } from 'fs'; +import { readPkcs12Truststore, readPkcs12Keystore } from '../../../../../../src/core/utils'; + +const KEY = 'xpack.monitoring.elasticsearch'; + +/* + * Parse a config object's Elasticsearch configuration, reading any + * certificates/keys from the filesystem + * + * TODO: this code can be removed when this plugin is migrated to the Kibana Platform, + * at that point the ElasticsearchClient and ElasticsearchConfig should be used instead + */ +export const parseElasticsearchConfig = (config: any) => { + const es = config.get(KEY); + + const errorPrefix = `[config validation of [${KEY}].ssl]`; + if (es.ssl?.key && es.ssl?.keystore?.path) { + throw new Error(`${errorPrefix}: cannot use [key] when [keystore.path] is specified`); + } + if (es.ssl?.certificate && es.ssl?.keystore?.path) { + throw new Error(`${errorPrefix}: cannot use [certificate] when [keystore.path] is specified`); + } + + const { alwaysPresentCertificate, verificationMode } = es.ssl; + const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(es); + + return { + ...es, + ssl: { + alwaysPresentCertificate, + key, + keyPassphrase, + certificate, + certificateAuthorities, + verificationMode, + }, + }; +}; + +const readKeyAndCerts = (rawConfig: any) => { + let key: string | undefined; + let keyPassphrase: string | undefined; + let certificate: string | undefined; + let certificateAuthorities: string[] | undefined; + + const addCAs = (ca: string[] | undefined) => { + if (ca && ca.length) { + certificateAuthorities = [...(certificateAuthorities || []), ...ca]; + } + }; + + if (rawConfig.ssl.keystore?.path) { + const keystore = readPkcs12Keystore( + rawConfig.ssl.keystore.path, + rawConfig.ssl.keystore.password + ); + if (!keystore.key) { + throw new Error(`Did not find key in Elasticsearch keystore.`); + } else if (!keystore.cert) { + throw new Error(`Did not find certificate in Elasticsearch keystore.`); + } + key = keystore.key; + certificate = keystore.cert; + addCAs(keystore.ca); + } else { + if (rawConfig.ssl.key) { + key = readFile(rawConfig.ssl.key); + keyPassphrase = rawConfig.ssl.keyPassphrase; + } + if (rawConfig.ssl.certificate) { + certificate = readFile(rawConfig.ssl.certificate); + } + } + + if (rawConfig.ssl.truststore?.path) { + const ca = readPkcs12Truststore( + rawConfig.ssl.truststore.path, + rawConfig.ssl.truststore.password + ); + addCAs(ca); + } + + const ca = rawConfig.ssl.certificateAuthorities; + if (ca) { + const parsed: string[] = []; + const paths = Array.isArray(ca) ? ca : [ca]; + if (paths.length > 0) { + for (const path of paths) { + parsed.push(readFile(path)); + } + addCAs(parsed); + } + } + + return { + key, + keyPassphrase, + certificate, + certificateAuthorities, + }; +}; + +const readFile = (file: string) => { + return readFileSync(file, 'utf8'); +}; diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index e26dd96dde1bf7..163bc43945be1c 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -11,6 +11,7 @@ import { instantiateClient } from './es_client/instantiate_client'; import { initMonitoringXpackInfo } from './init_monitoring_xpack_info'; import { initBulkUploader, registerCollectors } from './kibana_monitoring'; import { registerMonitoringCollection } from './telemetry_collection'; +import { parseElasticsearchConfig } from './es_client/parse_elasticsearch_config'; export class Plugin { setup(core, plugins) { @@ -36,6 +37,12 @@ export class Plugin { * fetch methods and uploads to the ES monitoring bulk endpoint */ const xpackMainPlugin = plugins.xpack_main; + + /* + * Parse the Elasticsearch config and read any certificates/keys if necessary + */ + const elasticsearchConfig = parseElasticsearchConfig(config); + xpackMainPlugin.status.once('green', async () => { // first time xpack_main turns green /* @@ -47,7 +54,7 @@ export class Plugin { await instantiateClient({ log: core.log, events: core.events, - config, + elasticsearchConfig, elasticsearchPlugin: plugins.elasticsearch, }); // Instantiate the dedicated ES client await initMonitoringXpackInfo({ From 01fe8afb987fc80125e84d965c5534bdcdc6e409 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 21 Jan 2020 17:35:47 +0000 Subject: [PATCH 24/59] [ML] Fixing categorization wizard example results (#54924) * [ML] Fixing categorization wizard example results * moving validation results to class * cleaning up category analyzer types * small tweaks * removing commented out code * fixing string ids * small refactor * improving validation messages * fixing types * updating message text * fixing typo * adding privileges error * updating privilege message * changes based on review * removing old warning message * fixing translations * renaming enum --- .../plugins/ml/common/constants/new_job.ts | 7 + .../plugins/ml/common/types/categories.ts | 29 ++ .../ml/common/util/string_utils.test.ts | 24 +- .../plugins/ml/common/util/string_utils.ts | 5 + .../job_creator/categorization_job_creator.ts | 53 +-- .../common/job_validator/job_validator.ts | 4 +- .../categorization_examples_loader.ts | 25 +- .../new_job/common/results_loader/index.ts | 2 +- .../examples_valid_callout.tsx | 64 ++-- .../categorization_view/field_examples.tsx | 4 +- .../categorization_view/metric_selection.tsx | 34 +- .../services/ml_api_service/index.d.ts | 15 +- .../application/services/ml_server_info.ts | 8 +- .../ml/server/models/job_service/index.js | 7 +- .../job_service/new_job/categorization.ts | 314 ------------------ .../new_job/categorization/examples.ts | 206 ++++++++++++ .../new_job/categorization/index.ts | 8 + .../new_job/categorization/top_categories.ts | 164 +++++++++ .../categorization/validation_results.ts | 208 ++++++++++++ .../models/job_service/new_job/index.ts | 2 +- 20 files changed, 763 insertions(+), 420 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts create mode 100644 x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts create mode 100644 x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts create mode 100644 x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts create mode 100644 x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts diff --git a/x-pack/legacy/plugins/ml/common/constants/new_job.ts b/x-pack/legacy/plugins/ml/common/constants/new_job.ts index 3c98b372afdf73..862fa72d11fdb7 100644 --- a/x-pack/legacy/plugins/ml/common/constants/new_job.ts +++ b/x-pack/legacy/plugins/ml/common/constants/new_job.ts @@ -26,7 +26,14 @@ export const DEFAULT_QUERY_DELAY = '60s'; export const SHARED_RESULTS_INDEX_NAME = 'shared'; +// Categorization export const NUMBER_OF_CATEGORY_EXAMPLES = 5; export const CATEGORY_EXAMPLES_SAMPLE_SIZE = 1000; export const CATEGORY_EXAMPLES_WARNING_LIMIT = 0.75; export const CATEGORY_EXAMPLES_ERROR_LIMIT = 0.02; + +export enum CATEGORY_EXAMPLES_VALIDATION_STATUS { + VALID = 'valid', + PARTIALLY_VALID = 'partially_valid', + INVALID = 'invalid', +} diff --git a/x-pack/legacy/plugins/ml/common/types/categories.ts b/x-pack/legacy/plugins/ml/common/types/categories.ts index 6ccd13ed9a39ec..765053ced52012 100644 --- a/x-pack/legacy/plugins/ml/common/types/categories.ts +++ b/x-pack/legacy/plugins/ml/common/types/categories.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../constants/new_job'; + export type CategoryId = number; export interface Category { @@ -23,3 +25,30 @@ export interface Token { type: string; position: number; } + +export interface CategorizationAnalyzer { + char_filter?: any[]; + tokenizer?: string; + filter?: any[]; + analyzer?: string; +} + +export interface CategoryFieldExample { + text: string; + tokens: Token[]; +} + +export enum VALIDATION_RESULT { + TOKEN_COUNT, + MEDIAN_LINE_LENGTH, + NULL_VALUES, + TOO_MANY_TOKENS, + FAILED_TO_TOKENIZE, + INSUFFICIENT_PRIVILEGES, +} + +export interface FieldExampleCheck { + id: VALIDATION_RESULT; + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS; + message: string; +} diff --git a/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts b/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts index aba2dbd230ada3..026c8e6110c993 100644 --- a/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts +++ b/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts @@ -4,7 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderTemplate } from './string_utils'; +import { renderTemplate, getMedianStringLength } from './string_utils'; + +const strings: string[] = [ + 'foo', + 'foofoofoofoofoo', + 'foofoofoo', + 'f', + 'f', + 'foofoofoofoofoofoofoo', +]; +const noStrings: string[] = []; describe('ML - string utils', () => { describe('renderTemplate', () => { @@ -24,4 +34,16 @@ describe('ML - string utils', () => { expect(result).toBe('string with 1 replacement, and a 2nd one.'); }); }); + + describe('getMedianStringLength', () => { + test('test median for string array', () => { + const result = getMedianStringLength(strings); + expect(result).toBe(9); + }); + + test('test median for no strings', () => { + const result = getMedianStringLength(noStrings); + expect(result).toBe(0); + }); + }); }); diff --git a/x-pack/legacy/plugins/ml/common/util/string_utils.ts b/x-pack/legacy/plugins/ml/common/util/string_utils.ts index 432baabe773cc1..9dd2ce3d74cd5d 100644 --- a/x-pack/legacy/plugins/ml/common/util/string_utils.ts +++ b/x-pack/legacy/plugins/ml/common/util/string_utils.ts @@ -17,3 +17,8 @@ export function renderTemplate(str: string, data?: Record): stri return str; } + +export function getMedianStringLength(strings: string[]) { + const sortedStringLengths = strings.map(s => s.length).sort((a, b) => a - b); + return sortedStringLengths[Math.floor(sortedStringLengths.length / 2)] || 0; +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index 71619311c4361d..0ff0ffb6f3bb39 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -16,25 +16,31 @@ import { CREATED_BY_LABEL, DEFAULT_BUCKET_SPAN, DEFAULT_RARE_BUCKET_SPAN, + CATEGORY_EXAMPLES_VALIDATION_STATUS, } from '../../../../../../common/constants/new_job'; import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; +import { + CategorizationAnalyzer, + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../../../common/types/categories'; import { getRichDetectors } from './util/general'; -import { CategorizationExamplesLoader, CategoryExample } from '../results_loader'; -import { CategorizationAnalyzer, getNewJobDefaults } from '../../../../services/ml_server_info'; - -type CategorizationAnalyzerType = CategorizationAnalyzer | null; +import { CategorizationExamplesLoader } from '../results_loader'; +import { getNewJobDefaults } from '../../../../services/ml_server_info'; export class CategorizationJobCreator extends JobCreator { protected _type: JOB_TYPE = JOB_TYPE.CATEGORIZATION; private _createCountDetector: () => void = () => {}; private _createRareDetector: () => void = () => {}; private _examplesLoader: CategorizationExamplesLoader; - private _categoryFieldExamples: CategoryExample[] = []; - private _categoryFieldValid: number = 0; + private _categoryFieldExamples: CategoryFieldExample[] = []; + private _validationChecks: FieldExampleCheck[] = []; + private _overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS = + CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; private _detectorType: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE = ML_JOB_AGGREGATION.COUNT; - private _categorizationAnalyzer: CategorizationAnalyzerType = null; - private _defaultCategorizationAnalyzer: CategorizationAnalyzerType; + private _categorizationAnalyzer: CategorizationAnalyzer = {}; + private _defaultCategorizationAnalyzer: CategorizationAnalyzer; constructor( indexPattern: IndexPattern, @@ -46,7 +52,7 @@ export class CategorizationJobCreator extends JobCreator { this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query); const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); - this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || null; + this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || {}; } public setDefaultDetectorProperties( @@ -93,7 +99,7 @@ export class CategorizationJobCreator extends JobCreator { } else { delete this._job_config.analysis_config.categorization_field_name; this._categoryFieldExamples = []; - this._categoryFieldValid = 0; + this._validationChecks = []; } } @@ -102,31 +108,38 @@ export class CategorizationJobCreator extends JobCreator { } public async loadCategorizationFieldExamples() { - const { valid, examples, sampleSize } = await this._examplesLoader.loadExamples(); + const { + examples, + sampleSize, + overallValidStatus, + validationChecks, + } = await this._examplesLoader.loadExamples(); this._categoryFieldExamples = examples; - this._categoryFieldValid = valid; - return { valid, examples, sampleSize }; + this._validationChecks = validationChecks; + this._overallValidStatus = overallValidStatus; + return { examples, sampleSize, overallValidStatus, validationChecks }; } public get categoryFieldExamples() { return this._categoryFieldExamples; } - public get categoryFieldValid() { - return this._categoryFieldValid; + public get validationChecks() { + return this._validationChecks; + } + + public get overallValidStatus() { + return this._overallValidStatus; } public get selectedDetectorType() { return this._detectorType; } - public set categorizationAnalyzer(analyzer: CategorizationAnalyzerType) { + public set categorizationAnalyzer(analyzer: CategorizationAnalyzer) { this._categorizationAnalyzer = analyzer; - if ( - analyzer === null || - isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer) - ) { + if (isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer)) { delete this._job_config.analysis_config.categorization_analyzer; } else { this._job_config.analysis_config.categorization_analyzer = analyzer; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts index 976e94b377ae8b..8f6b16c407fb66 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts @@ -16,7 +16,7 @@ import { JobCreator, JobCreatorType, isCategorizationJobCreator } from '../job_c import { populateValidationMessages, checkForExistingJobAndGroupIds } from './util'; import { ExistingJobsAndGroups } from '../../../../services/job_service'; import { cardinalityValidator, CardinalityValidatorResult } from './validators'; -import { CATEGORY_EXAMPLES_ERROR_LIMIT } from '../../../../../../common/constants/new_job'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../common/constants/new_job'; // delay start of validation to allow the user to make changes // e.g. if they are typing in a new value, try not to validate @@ -207,7 +207,7 @@ export class JobValidator { private _runAdvancedValidation() { if (isCategorizationJobCreator(this._jobCreator)) { this._advancedValidations.categorizationFieldValid.valid = - this._jobCreator.categoryFieldValid > CATEGORY_EXAMPLES_ERROR_LIMIT; + this._jobCreator.overallValidStatus !== CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts index ce1ea0bdaf181c..62a4d070fec328 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts @@ -6,15 +6,12 @@ import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; -import { Token } from '../../../../../../common/types/categories'; import { CategorizationJobCreator } from '../job_creator'; import { ml } from '../../../../services/ml_api_service'; -import { NUMBER_OF_CATEGORY_EXAMPLES } from '../../../../../../common/constants/new_job'; - -export interface CategoryExample { - text: string; - tokens: Token[]; -} +import { + NUMBER_OF_CATEGORY_EXAMPLES, + CATEGORY_EXAMPLES_VALIDATION_STATUS, +} from '../../../../../../common/constants/new_job'; export class CategorizationExamplesLoader { private _jobCreator: CategorizationJobCreator; @@ -36,20 +33,22 @@ export class CategorizationExamplesLoader { const analyzer = this._jobCreator.categorizationAnalyzer; const categorizationFieldName = this._jobCreator.categorizationFieldName; if (categorizationFieldName === null) { - return { valid: 0, examples: [], sampleSize: 0 }; + return { + examples: [], + sampleSize: 0, + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + validationChecks: [], + }; } - const start = Math.floor( - this._jobCreator.start + (this._jobCreator.end - this._jobCreator.start) / 2 - ); const resp = await ml.jobs.categorizationFieldExamples( this._indexPatternTitle, this._query, NUMBER_OF_CATEGORY_EXAMPLES, categorizationFieldName, this._timeFieldName, - start, - 0, + this._jobCreator.start, + this._jobCreator.end, analyzer ); return resp; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts index 724c62f22e469f..e15d859f8e6c31 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts @@ -5,4 +5,4 @@ */ export { ResultsLoader, Results, ModelItem, Anomaly } from './results_loader'; -export { CategorizationExamplesLoader, CategoryExample } from './categorization_examples_loader'; +export { CategorizationExamplesLoader } from './categorization_examples_loader'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx index 270ba99d938cdc..ac886a3aea61a7 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx @@ -9,27 +9,24 @@ import { EuiCallOut, EuiSpacer, EuiCallOutProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CategorizationAnalyzer } from '../../../../../../../services/ml_server_info'; -import { EditCategorizationAnalyzerFlyout } from '../../../common/edit_categorization_analyzer_flyout'; import { - CATEGORY_EXAMPLES_ERROR_LIMIT, - CATEGORY_EXAMPLES_WARNING_LIMIT, -} from '../../../../../../../../../common/constants/new_job'; - -type CategorizationAnalyzerType = CategorizationAnalyzer | null; + CategorizationAnalyzer, + FieldExampleCheck, +} from '../../../../../../../../../common/types/categories'; +import { EditCategorizationAnalyzerFlyout } from '../../../common/edit_categorization_analyzer_flyout'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../../../../common/constants/new_job'; interface Props { - examplesValid: number; - sampleSize: number; - categorizationAnalyzer: CategorizationAnalyzerType; + validationChecks: FieldExampleCheck[]; + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS; + categorizationAnalyzer: CategorizationAnalyzer; } export const ExamplesValidCallout: FC = ({ - examplesValid, + overallValidStatus, + validationChecks, categorizationAnalyzer, - sampleSize, }) => { - const percentageText = ; const analyzerUsed = ; let color: EuiCallOutProps['color'] = 'success'; @@ -40,7 +37,7 @@ export const ExamplesValidCallout: FC = ({ } ); - if (examplesValid < CATEGORY_EXAMPLES_ERROR_LIMIT) { + if (overallValidStatus === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID) { color = 'danger'; title = i18n.translate( 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.invalid', @@ -48,7 +45,7 @@ export const ExamplesValidCallout: FC = ({ defaultMessage: 'Selected category field is invalid', } ); - } else if (examplesValid < CATEGORY_EXAMPLES_WARNING_LIMIT) { + } else if (overallValidStatus === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID) { color = 'warning'; title = i18n.translate( 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.possiblyInvalid', @@ -60,45 +57,24 @@ export const ExamplesValidCallout: FC = ({ return ( - {percentageText} + {validationChecks.map((v, i) => ( +
{v.message}
+ ))} {analyzerUsed}
); }; -const PercentageText: FC<{ examplesValid: number; sampleSize: number }> = ({ - examplesValid, - sampleSize, -}) => ( -
- -
-); - -const AnalyzerUsed: FC<{ categorizationAnalyzer: CategorizationAnalyzerType }> = ({ +const AnalyzerUsed: FC<{ categorizationAnalyzer: CategorizationAnalyzer }> = ({ categorizationAnalyzer, }) => { let analyzer = ''; - if (typeof categorizationAnalyzer === null) { - return null; - } - if (typeof categorizationAnalyzer === 'string') { - analyzer = categorizationAnalyzer; - } else { - if (categorizationAnalyzer?.tokenizer !== undefined) { - analyzer = categorizationAnalyzer?.tokenizer!; - } else if (categorizationAnalyzer?.analyzer !== undefined) { - analyzer = categorizationAnalyzer?.analyzer!; - } + if (categorizationAnalyzer?.tokenizer !== undefined) { + analyzer = categorizationAnalyzer.tokenizer; + } else if (categorizationAnalyzer?.analyzer !== undefined) { + analyzer = categorizationAnalyzer.analyzer; } return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx index 7f9b2e43b90050..51cea179a6c0d9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx @@ -7,10 +7,10 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiText } from '@elastic/eui'; -import { CategoryExample } from '../../../../../common/results_loader'; +import { CategoryFieldExample } from '../../../../../../../../../common/types/categories'; interface Props { - fieldExamples: CategoryExample[] | null; + fieldExamples: CategoryFieldExample[] | null; } const TOKEN_HIGHLIGHT_COLOR = '#b0ccf7'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx index 52b5c61e70fe5e..411f6e898bd486 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx @@ -14,7 +14,11 @@ import { CategorizationField } from '../categorization_field'; import { CategorizationDetector } from '../categorization_detector'; import { FieldExamples } from './field_examples'; import { ExamplesValidCallout } from './examples_valid_callout'; -import { CategoryExample } from '../../../../../common/results_loader'; +import { + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../../../../../../common/types/categories'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../../../../common/constants/new_job'; import { LoadingWrapper } from '../../../charts/loading_wrapper'; interface Props { @@ -31,9 +35,11 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { const [categorizationAnalyzerString, setCategorizationAnalyzerString] = useState( JSON.stringify(jobCreator.categorizationAnalyzer) ); - const [fieldExamples, setFieldExamples] = useState(null); - const [examplesValid, setExamplesValid] = useState(0); - const [sampleSize, setSampleSize] = useState(0); + const [fieldExamples, setFieldExamples] = useState(null); + const [overallValidStatus, setOverallValidStatus] = useState( + CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID + ); + const [validationChecks, setValidationChecks] = useState([]); const [categorizationFieldName, setCategorizationFieldName] = useState( jobCreator.categorizationFieldName @@ -73,28 +79,32 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { setLoadingData(true); try { const { - valid, examples, - sampleSize: tempSampleSize, + overallValidStatus: tempOverallValidStatus, + validationChecks: tempValidationChecks, } = await jobCreator.loadCategorizationFieldExamples(); setFieldExamples(examples); - setExamplesValid(valid); + setOverallValidStatus(tempOverallValidStatus); + setValidationChecks(tempValidationChecks); setLoadingData(false); - setSampleSize(tempSampleSize); } catch (error) { setLoadingData(false); + setFieldExamples(null); + setValidationChecks([]); + setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID); mlMessageBarService.notify.error(error); } } else { setFieldExamples(null); - setExamplesValid(0); + setValidationChecks([]); + setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID); } setIsValid(categorizationFieldName !== null); } useEffect(() => { jobCreatorUpdate(); - }, [examplesValid]); + }, [overallValidStatus]); return ( <> @@ -109,8 +119,8 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { {fieldExamples !== null && loadingData === false && ( <> diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts index db9d158c0ead99..6420b60e4c8380 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts @@ -22,6 +22,12 @@ import { PartitionFieldsDefinition } from '../results_service/result_service_rx' import { annotations } from './annotations'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; import { CombinedJob, JobId } from '../../jobs/new_job/common/job_creator/configs'; +import { + CategorizationAnalyzer, + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../common/types/categories'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../common/constants/new_job'; // TODO This is not a complete representation of all methods of `ml.*`. // It just satisfies needs for other parts of the code area which use @@ -184,8 +190,13 @@ declare interface Ml { timeField: string | undefined, start: number, end: number, - analyzer: any - ): Promise<{ valid: number; examples: any[]; sampleSize: number }>; + analyzer: CategorizationAnalyzer + ): Promise<{ + examples: CategoryFieldExample[]; + sampleSize: number; + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS; + validationChecks: FieldExampleCheck[]; + }>; topCategories( jobId: string, count: number diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts index 6bf5a7b0c97433..304778281c2f2f 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts @@ -5,6 +5,7 @@ */ import { ml } from './ml_api_service'; +import { CategorizationAnalyzer } from '../../../common/types/categories'; export interface MlServerDefaults { anomaly_detectors: { @@ -16,13 +17,6 @@ export interface MlServerDefaults { datafeeds: { scroll_size?: number }; } -export interface CategorizationAnalyzer { - char_filter?: any[]; - tokenizer?: string; - filter?: any[]; - analyzer?: string; -} - export interface MlServerLimits { max_model_memory_limit?: string; } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/index.js b/x-pack/legacy/plugins/ml/server/models/job_service/index.js index 186bcbae84546f..5c0eff3112a53e 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/index.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/index.js @@ -8,7 +8,11 @@ import { datafeedsProvider } from './datafeeds'; import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; import { newJobCapsProvider } from './new_job_caps'; -import { newJobChartsProvider, categorizationExamplesProvider } from './new_job'; +import { + newJobChartsProvider, + categorizationExamplesProvider, + topCategoriesProvider, +} from './new_job'; export function jobServiceProvider(callWithRequest, request) { return { @@ -18,5 +22,6 @@ export function jobServiceProvider(callWithRequest, request) { ...newJobCapsProvider(callWithRequest, request), ...newJobChartsProvider(callWithRequest, request), ...categorizationExamplesProvider(callWithRequest, request), + ...topCategoriesProvider(callWithRequest, request), }; } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts deleted file mode 100644 index b3c70bf589cd04..00000000000000 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { chunk } from 'lodash'; -import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; -import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../common/constants/new_job'; -import { CategoryId, Category, Token } from '../../../../common/types/categories'; -import { callWithRequestType } from '../../../../common/types/kibana'; - -const VALID_TOKEN_COUNT = 3; -const CHUNK_SIZE = 100; - -export function categorizationExamplesProvider(callWithRequest: callWithRequestType) { - async function categorizationExamples( - indexPatternTitle: string, - query: any, - size: number, - categorizationFieldName: string, - timeField: string | undefined, - start: number, - end: number, - analyzer?: any - ) { - if (timeField !== undefined) { - const range = { - range: { - [timeField]: { - gte: start, - format: 'epoch_millis', - }, - }, - }; - - if (query.bool === undefined) { - query.bool = {}; - } - if (query.bool.filter === undefined) { - query.bool.filter = range; - } else { - if (Array.isArray(query.bool.filter)) { - query.bool.filter.push(range); - } else { - query.bool.filter.range = range; - } - } - } - - const results = await callWithRequest('search', { - index: indexPatternTitle, - size, - body: { - _source: categorizationFieldName, - query, - }, - }); - const examples: string[] = results.hits?.hits - ?.map((doc: any) => doc._source[categorizationFieldName]) - .filter((example: string | null | undefined) => example !== undefined && example !== null); - - async function loadTokens(chunkSize: number) { - const exampleChunks = chunk(examples, chunkSize); - const tokensPerChunks = await Promise.all(exampleChunks.map(c => getTokens(c, analyzer))); - const tokensPerExample = tokensPerChunks.flat(); - return examples.map((e, i) => ({ text: e, tokens: tokensPerExample[i] })); - } - try { - return loadTokens(CHUNK_SIZE); - } catch (error) { - // if an error is thrown when loading the tokens, lower the chunk size by half and try again - // the error may have been caused by too many tokens being found. - // the _analyze endpoint has a maximum of 10000 tokens. - return loadTokens(CHUNK_SIZE / 2); - } - } - - async function getTokens(examples: string[], analyzer?: any) { - const { tokens }: { tokens: Token[] } = await callWithRequest('indices.analyze', { - body: { - ...getAnalyzer(analyzer), - text: examples, - }, - }); - - const lengths = examples.map(e => e.length); - const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); - - const tokensPerExample: Token[][] = examples.map(e => []); - - tokens.forEach((t, i) => { - for (let g = 0; g < sumLengths.length; g++) { - if (t.start_offset <= sumLengths[g] + g) { - const offset = g > 0 ? sumLengths[g - 1] + g : 0; - tokensPerExample[g].push({ - ...t, - start_offset: t.start_offset - offset, - end_offset: t.end_offset - offset, - }); - break; - } - } - }); - return tokensPerExample; - } - - function getAnalyzer(analyzer: any) { - if (typeof analyzer === 'object' && analyzer.tokenizer !== undefined) { - return analyzer; - } else { - return { analyzer: 'standard' }; - } - } - - async function validateCategoryExamples( - indexPatternTitle: string, - query: any, - size: number, - categorizationFieldName: string, - timeField: string | undefined, - start: number, - end: number, - analyzer?: any - ) { - const resp = await categorizationExamples( - indexPatternTitle, - query, - CATEGORY_EXAMPLES_SAMPLE_SIZE, - categorizationFieldName, - timeField, - start, - end, - analyzer - ); - - const sortedExamples = resp - .map((e, i) => ({ ...e, origIndex: i })) - .sort((a, b) => b.tokens.length - a.tokens.length); - const validExamples = sortedExamples.filter(e => e.tokens.length >= VALID_TOKEN_COUNT); - const sampleSize = sortedExamples.length; - - const multiple = Math.floor(sampleSize / size) || sampleSize; - const filteredExamples = []; - let i = 0; - while (filteredExamples.length < size && i < sortedExamples.length) { - filteredExamples.push(sortedExamples[i]); - i += multiple; - } - const examples = filteredExamples - .sort((a, b) => a.origIndex - b.origIndex) - .map(e => ({ text: e.text, tokens: e.tokens })); - - return { - sampleSize, - valid: sortedExamples.length === 0 ? 0 : validExamples.length / sortedExamples.length, - examples, - }; - } - - async function getTotalCategories(jobId: string): Promise<{ total: number }> { - const totalResp = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - { - exists: { - field: 'category_id', - }, - }, - ], - }, - }, - }, - }); - return totalResp?.hits?.total?.value ?? 0; - } - - async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { - const top = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - { - term: { - result_type: 'model_plot', - }, - }, - { - term: { - by_field_name: 'mlcategory', - }, - }, - ], - }, - }, - aggs: { - cat_count: { - terms: { - field: 'by_field_value', - size: numberOfCategories, - }, - }, - }, - }, - }); - - const catCounts: Array<{ - id: CategoryId; - count: number; - }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ - id: c.key, - count: c.doc_count, - })); - return catCounts || []; - } - - async function getCategories( - jobId: string, - catIds: CategoryId[], - size: number - ): Promise { - const categoryFilter = catIds.length - ? { - terms: { - category_id: catIds, - }, - } - : { - exists: { - field: 'category_id', - }, - }; - const result = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - categoryFilter, - ], - }, - }, - }, - }); - - return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; - } - - async function topCategories(jobId: string, numberOfCategories: number) { - const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); - const categories = await getCategories( - jobId, - catCounts.map(c => c.id), - catCounts.length || numberOfCategories - ); - - const catsById = categories.reduce((p, c) => { - p[c.category_id] = c; - return p; - }, {} as { [id: number]: Category }); - - const total = await getTotalCategories(jobId); - - if (catCounts.length) { - return { - total, - categories: catCounts.map(({ id, count }) => { - return { - count, - category: catsById[id] ?? null, - }; - }), - }; - } else { - return { - total, - categories: categories.map(category => { - return { - category, - }; - }), - }; - } - } - - return { - categorizationExamples, - validateCategoryExamples, - topCategories, - }; -} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts new file mode 100644 index 00000000000000..76473bd55db7fb --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { chunk } from 'lodash'; +import { SearchResponse } from 'elasticsearch'; +import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/new_job'; +import { + Token, + CategorizationAnalyzer, + CategoryFieldExample, +} from '../../../../../common/types/categories'; +import { callWithRequestType } from '../../../../../common/types/kibana'; +import { ValidationResults } from './validation_results'; + +const CHUNK_SIZE = 100; + +export function categorizationExamplesProvider(callWithRequest: callWithRequestType) { + const validationResults = new ValidationResults(); + + async function categorizationExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer: CategorizationAnalyzer + ): Promise<{ examples: CategoryFieldExample[]; error?: any }> { + if (timeField !== undefined) { + const range = { + range: { + [timeField]: { + gte: start, + lt: end, + format: 'epoch_millis', + }, + }, + }; + if (query.bool === undefined) { + query.bool = {}; + } + if (query.bool.filter === undefined) { + query.bool.filter = range; + } else { + if (Array.isArray(query.bool.filter)) { + query.bool.filter.push(range); + } else { + query.bool.filter.range = range; + } + } + } + + const results: SearchResponse<{ [id: string]: string }> = await callWithRequest('search', { + index: indexPatternTitle, + size, + body: { + _source: categorizationFieldName, + query, + sort: ['_doc'], + }, + }); + + const tempExamples = results.hits.hits.map(({ _source }) => _source[categorizationFieldName]); + + validationResults.createNullValueResult(tempExamples); + + const allExamples = tempExamples.filter( + (example: string | null | undefined) => example !== undefined && example !== null + ); + + validationResults.createMedianMessageLengthResult(allExamples); + + try { + const examplesWithTokens = await getTokens(CHUNK_SIZE, allExamples, analyzer); + return { examples: examplesWithTokens }; + } catch (err) { + // console.log('dropping to 50 chunk size'); + // if an error is thrown when loading the tokens, lower the chunk size by half and try again + // the error may have been caused by too many tokens being found. + // the _analyze endpoint has a maximum of 10000 tokens. + const halfExamples = allExamples.splice(0, Math.ceil(allExamples.length / 2)); + const halfChunkSize = CHUNK_SIZE / 2; + try { + const examplesWithTokens = await getTokens(halfChunkSize, halfExamples, analyzer); + return { examples: examplesWithTokens }; + } catch (error) { + validationResults.createTooManyTokensResult(error, halfChunkSize); + return { examples: halfExamples.map(e => ({ text: e, tokens: [] })) }; + } + } + } + + async function getTokens( + chunkSize: number, + examples: string[], + analyzer: CategorizationAnalyzer + ): Promise { + const exampleChunks = chunk(examples, chunkSize); + const tokensPerExampleChunks: Token[][][] = []; + for (const c of exampleChunks) { + tokensPerExampleChunks.push(await loadTokens(c, analyzer)); + } + const tokensPerExample = tokensPerExampleChunks.flat(); + return examples.map((e, i) => ({ text: e, tokens: tokensPerExample[i] })); + } + + async function loadTokens(examples: string[], analyzer: CategorizationAnalyzer) { + const { tokens }: { tokens: Token[] } = await callWithRequest('indices.analyze', { + body: { + ...getAnalyzer(analyzer), + text: examples, + }, + }); + + const lengths = examples.map(e => e.length); + const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); + + const tokensPerExample: Token[][] = examples.map(e => []); + + tokens.forEach((t, i) => { + for (let g = 0; g < sumLengths.length; g++) { + if (t.start_offset <= sumLengths[g] + g) { + const offset = g > 0 ? sumLengths[g - 1] + g : 0; + tokensPerExample[g].push({ + ...t, + start_offset: t.start_offset - offset, + end_offset: t.end_offset - offset, + }); + break; + } + } + }); + return tokensPerExample; + } + + function getAnalyzer(analyzer: CategorizationAnalyzer) { + if (typeof analyzer === 'object' && analyzer.tokenizer !== undefined) { + return analyzer; + } else { + return { analyzer: 'standard' }; + } + } + + async function validateCategoryExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer: CategorizationAnalyzer + ) { + const resp = await categorizationExamples( + indexPatternTitle, + query, + CATEGORY_EXAMPLES_SAMPLE_SIZE, + categorizationFieldName, + timeField, + start, + end, + analyzer + ); + + const { examples } = resp; + const sampleSize = examples.length; + validationResults.createTokenCountResult(examples, sampleSize); + + // sort examples by number of tokens, keeping track of their original order + // with an origIndex property + const sortedExamples = examples + .map((e, i) => ({ ...e, origIndex: i })) + .sort((a, b) => b.tokens.length - a.tokens.length); + + // we only want 'size' (e.g. 5) number of examples, + // so loop through the sorted examples, taking 5 at evenly + // spread intervals + const multiple = Math.floor(sampleSize / size) || sampleSize; + const filteredExamples = []; + let i = 0; + while (filteredExamples.length < size && i < sampleSize) { + filteredExamples.push(sortedExamples[i]); + i += multiple; + } + + // sort back into original order and remove origIndex property + const processedExamples = filteredExamples + .sort((a, b) => a.origIndex - b.origIndex) + .map(e => ({ text: e.text, tokens: e.tokens })); + + return { + overallValidStatus: validationResults.overallResult, + validationChecks: validationResults.results, + sampleSize, + examples: processedExamples, + }; + } + + return { + validateCategoryExamples, + }; +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts new file mode 100644 index 00000000000000..be32b99b5e527e --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { categorizationExamplesProvider } from './examples'; +export { topCategoriesProvider } from './top_categories'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts new file mode 100644 index 00000000000000..3361cc454e2b7b --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns'; +import { CategoryId, Category } from '../../../../../common/types/categories'; +import { callWithRequestType } from '../../../../../common/types/kibana'; + +export function topCategoriesProvider(callWithRequest: callWithRequestType) { + async function getTotalCategories(jobId: string): Promise<{ total: number }> { + const totalResp = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + exists: { + field: 'category_id', + }, + }, + ], + }, + }, + }, + }); + return totalResp?.hits?.total?.value ?? 0; + } + + async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { + const top: SearchResponse = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + term: { + result_type: 'model_plot', + }, + }, + { + term: { + by_field_name: 'mlcategory', + }, + }, + ], + }, + }, + aggs: { + cat_count: { + terms: { + field: 'by_field_value', + size: numberOfCategories, + }, + }, + }, + }, + }); + + const catCounts: Array<{ + id: CategoryId; + count: number; + }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ + id: c.key, + count: c.doc_count, + })); + return catCounts || []; + } + + async function getCategories( + jobId: string, + catIds: CategoryId[], + size: number + ): Promise { + const categoryFilter = catIds.length + ? { + terms: { + category_id: catIds, + }, + } + : { + exists: { + field: 'category_id', + }, + }; + const result: SearchResponse = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + categoryFilter, + ], + }, + }, + }, + }); + + return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; + } + + async function topCategories(jobId: string, numberOfCategories: number) { + const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); + const categories = await getCategories( + jobId, + catCounts.map(c => c.id), + catCounts.length || numberOfCategories + ); + + const catsById = categories.reduce((p, c) => { + p[c.category_id] = c; + return p; + }, {} as { [id: number]: Category }); + + const total = await getTotalCategories(jobId); + + if (catCounts.length) { + return { + total, + categories: catCounts.map(({ id, count }) => { + return { + count, + category: catsById[id] ?? null, + }; + }), + }; + } else { + return { + total, + categories: categories.map(category => { + return { + category, + }; + }), + }; + } + } + + return { + topCategories, + }; +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts new file mode 100644 index 00000000000000..e173b893dfbfa1 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + CATEGORY_EXAMPLES_VALIDATION_STATUS, + CATEGORY_EXAMPLES_ERROR_LIMIT, + CATEGORY_EXAMPLES_WARNING_LIMIT, +} from '../../../../../common/constants/new_job'; +import { + FieldExampleCheck, + CategoryFieldExample, + VALIDATION_RESULT, +} from '../../../../../common/types/categories'; +import { getMedianStringLength } from '../../../../../common/util/string_utils'; + +const VALID_TOKEN_COUNT = 3; +const MEDIAN_LINE_LENGTH_LIMIT = 400; +const NULL_COUNT_PERCENT_LIMIT = 0.75; + +export class ValidationResults { + private _results: FieldExampleCheck[] = []; + + public get results() { + return this._results; + } + + public get overallResult() { + if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID)) { + return CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; + } + if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID)) { + return CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID; + } + return CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; + } + + private _resultExists(id: VALIDATION_RESULT) { + return this._results.some(r => r.id === id); + } + + public createTokenCountResult(examples: CategoryFieldExample[], sampleSize: number) { + if (examples.length === 0) { + this.createNoExamplesResult(); + return; + } + + if (this._resultExists(VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES) === true) { + // if tokenizing has failed due to insufficient privileges, don't show + // the message about token count + return; + } + + const validExamplesSize = examples.filter(e => e.tokens.length >= VALID_TOKEN_COUNT).length; + const percentValid = sampleSize === 0 ? 0 : validExamplesSize / sampleSize; + + let valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; + if (percentValid < CATEGORY_EXAMPLES_ERROR_LIMIT) { + valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; + } else if (percentValid < CATEGORY_EXAMPLES_WARNING_LIMIT) { + valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID; + } + + const message = i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.tokenLengthValidation', + { + defaultMessage: + '{number} field {number, plural, zero {value} one {value} other {values}} analyzed, {percentage}% contain {validTokenCount} or more tokens.', + values: { + number: sampleSize, + percentage: Math.floor(percentValid * 100), + validTokenCount: VALID_TOKEN_COUNT, + }, + } + ); + + if ( + this._resultExists(VALIDATION_RESULT.TOO_MANY_TOKENS) === false && + this._resultExists(VALIDATION_RESULT.FAILED_TO_TOKENIZE) === false + ) { + this._results.unshift({ + id: VALIDATION_RESULT.TOKEN_COUNT, + valid, + message, + }); + } + } + + public createMedianMessageLengthResult(examples: string[]) { + const median = getMedianStringLength(examples); + + if (median > MEDIAN_LINE_LENGTH_LIMIT) { + this._results.push({ + id: VALIDATION_RESULT.MEDIAN_LINE_LENGTH, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.medianLineLength', + { + defaultMessage: + 'The median length for the field values analyzed is over {medianLimit} characters.', + values: { medianLimit: MEDIAN_LINE_LENGTH_LIMIT }, + } + ), + }); + } + } + + public createNoExamplesResult() { + this._results.push({ + id: VALIDATION_RESULT.NULL_VALUES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate('xpack.ml.models.jobService.categorization.messages.noDataFound', { + defaultMessage: + 'No examples for this field could be found. Please ensure the selected date range contains data.', + }), + }); + } + + public createNullValueResult(examples: Array) { + const nullCount = examples.filter(e => e === null).length; + + if (nullCount / examples.length >= NULL_COUNT_PERCENT_LIMIT) { + this._results.push({ + id: VALIDATION_RESULT.NULL_VALUES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate('xpack.ml.models.jobService.categorization.messages.nullValues', { + defaultMessage: 'More than {percent}% of field values are null.', + values: { percent: NULL_COUNT_PERCENT_LIMIT * 100 }, + }), + }); + } + } + + public createTooManyTokensResult(error: any, sampleSize: number) { + // expecting error message: + // The number of tokens produced by calling _analyze has exceeded the allowed maximum of [10000]. + // This limit can be set by changing the [index.analyze.max_token_count] index level setting. + + if (error.statusCode === 403) { + this.createPrivilegesErrorResult(error); + return; + } + const message: string = error.message; + if (message) { + const rxp = /exceeded the allowed maximum of \[(\d+?)\]/; + const match = rxp.exec(message); + if (match?.length === 2) { + const tokenLimit = match[1]; + this._results.push({ + id: VALIDATION_RESULT.TOO_MANY_TOKENS, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.tooManyTokens', + { + defaultMessage: + 'Tokenization of field value examples has failed due to more than {tokenLimit} tokens being found in a sample of {sampleSize} values.', + values: { sampleSize, tokenLimit }, + } + ), + }); + return; + } + return; + } + this.createFailureToTokenize(message); + } + + public createPrivilegesErrorResult(error: any) { + const message: string = error.message; + if (message) { + this._results.push({ + id: VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.insufficientPrivileges', + { + defaultMessage: + 'Tokenization of field value examples could not be performed due to insufficient privileges. Field values cannot therefore be checked to see if they are appropriate for use in a categorization job.', + } + ), + }); + this._results.push({ + id: VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message, + }); + return; + } + } + + public createFailureToTokenize(message: string | undefined) { + this._results.push({ + id: VALIDATION_RESULT.FAILED_TO_TOKENIZE, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.failureToGetTokens', + { + defaultMessage: + 'It was not possible to tokenize a sample of example field values. {message}', + values: { message: message || '' }, + } + ), + }); + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts index da23efa67d0b5c..da60a90f4bfbc1 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts @@ -5,4 +5,4 @@ */ export { newJobChartsProvider } from './charts'; -export { categorizationExamplesProvider } from './categorization'; +export { categorizationExamplesProvider, topCategoriesProvider } from './categorization'; From da54657b915c4710964dfdaf6678af59fc84d725 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Tue, 21 Jan 2020 12:48:07 -0500 Subject: [PATCH 25/59] Grouped Kibana nav (#53545) Adds concept of `category` to nav links, grouping them by this in the side nav --- .../kibana-plugin-public.appbase.category.md | 13 + .../public/kibana-plugin-public.appbase.md | 1 + ...ana-plugin-public.appcategory.arialabel.md | 13 + ...a-plugin-public.appcategory.euiicontype.md | 13 + .../kibana-plugin-public.appcategory.label.md | 13 + .../kibana-plugin-public.appcategory.md | 23 + .../kibana-plugin-public.appcategory.order.md | 13 + ...na-plugin-public.chromenavlink.category.md | 13 + .../kibana-plugin-public.chromenavlink.md | 1 + ...na-plugin-public.legacynavlink.category.md | 11 + .../kibana-plugin-public.legacynavlink.md | 1 + .../core/public/kibana-plugin-public.md | 1 + packages/kbn-i18n/src/loader.ts | 7 +- src/core/public/application/types.ts | 8 + src/core/public/chrome/chrome_service.test.ts | 2 + src/core/public/chrome/chrome_service.tsx | 6 +- src/core/public/chrome/nav_links/nav_link.ts | 6 + .../__snapshots__/nav_drawer.test.tsx.snap | 5224 +++++++++++++++++ src/core/public/chrome/ui/header/header.tsx | 349 +- .../public/chrome/ui/header/header_logo.tsx | 104 + src/core/public/chrome/ui/header/index.ts | 2 + .../chrome/ui/header/nav_drawer.test.tsx | 103 + .../public/chrome/ui/header/nav_drawer.tsx | 170 + src/core/public/chrome/ui/header/nav_link.tsx | 87 + .../public/chrome/ui/header/recent_links.tsx | 113 + src/core/public/core_system.ts | 1 + src/core/public/index.ts | 3 +- .../injected_metadata_service.ts | 4 + src/core/public/legacy/legacy_service.ts | 1 + src/core/public/public.api.md | 32 + .../plugins/find_legacy_plugin_specs.ts | 2 + src/core/server/legacy/types.ts | 2 +- src/core/types/app_category.ts | 52 + src/core/types/index.ts | 1 + src/core/utils/default_app_categories.ts | 48 + src/core/utils/index.ts | 1 + src/legacy/core_plugins/kibana/index.js | 10 +- .../components/fetch_error/fetch_error.tsx | 2 +- .../home/np_ready/components/home.test.js | 4 +- .../kibana/public/management/index.js | 12 +- .../kibana/ui_setting_defaults.js | 19 + src/legacy/core_plugins/management/index.ts | 2 +- .../telemetry/common/constants.ts | 2 +- .../telemetry_management_collector.ts | 4 +- src/legacy/core_plugins/timelion/index.ts | 2 + src/legacy/plugin_discovery/types.ts | 2 + .../ui/public/management/breadcrumbs.ts | 4 +- src/legacy/ui/ui_apps/ui_app.js | 4 + .../ui/ui_exports/ui_export_types/ui_apps.js | 2 + .../ui/ui_nav_links/__tests__/ui_nav_link.js | 1 + src/legacy/ui/ui_nav_links/ui_nav_link.js | 3 + .../management_sidebar_nav.tsx | 5 +- .../public/legacy/sections_register.js | 4 +- .../management/public/management_app.tsx | 3 +- .../dashboard/create_and_add_embeddables.js | 5 +- .../apps/management/_index_pattern_filter.js | 2 +- test/functional/apps/visualize/_lab_mode.js | 5 +- test/functional/page_objects/header_page.js | 4 +- .../{settings_page.js => settings_page.ts} | 93 +- .../core_plugins/application_status.ts | 6 +- .../test_suites/core_plugins/applications.ts | 2 +- x-pack/legacy/plugins/apm/index.ts | 5 +- x-pack/legacy/plugins/canvas/index.js | 2 + x-pack/legacy/plugins/dashboard_mode/index.js | 9 +- x-pack/legacy/plugins/graph/index.ts | 2 + .../guidance_panel/guidance_panel.tsx | 2 +- x-pack/legacy/plugins/infra/index.ts | 3 + x-pack/legacy/plugins/maps/index.js | 6 +- x-pack/legacy/plugins/ml/index.ts | 3 +- .../legacy/plugins/monitoring/ui_exports.js | 2 + x-pack/legacy/plugins/siem/index.ts | 2 + x-pack/legacy/plugins/uptime/index.ts | 2 + .../translations/translations/ja-JP.json | 5 +- .../translations/translations/zh-CN.json | 5 +- .../advanced_settings_security.ts | 8 +- .../advanced_settings_spaces.ts | 3 +- .../apps/apm/feature_controls/apm_security.ts | 4 +- .../apps/apm/feature_controls/apm_spaces.ts | 3 +- .../feature_controls/canvas_security.ts | 4 +- .../canvas/feature_controls/canvas_spaces.ts | 3 +- .../feature_controls/dashboard_security.ts | 4 +- .../feature_controls/dashboard_spaces.ts | 9 +- .../dashboard_mode/dashboard_view_mode.js | 5 +- .../feature_controls/dev_tools_security.ts | 4 +- .../feature_controls/dev_tools_spaces.ts | 9 +- .../feature_controls/discover_security.ts | 4 +- .../feature_controls/discover_spaces.ts | 2 + .../graph/feature_controls/graph_security.ts | 4 +- .../graph/feature_controls/graph_spaces.ts | 3 +- .../index_patterns_security.ts | 8 +- .../feature_controls/index_patterns_spaces.ts | 3 +- .../infrastructure_security.ts | 4 +- .../feature_controls/infrastructure_spaces.ts | 10 +- .../infra/feature_controls/logs_security.ts | 4 +- .../infra/feature_controls/logs_spaces.ts | 9 +- .../feature_controls/ml_security.ts | 3 +- .../feature_controls/ml_spaces.ts | 3 +- .../maps/feature_controls/maps_security.ts | 6 +- .../feature_controls/monitoring_security.ts | 3 +- .../feature_controls/monitoring_spaces.ts | 5 +- .../feature_controls/spaces_security.ts | 5 +- .../feature_controls/timelion_security.ts | 4 +- .../feature_controls/timelion_spaces.ts | 9 +- .../feature_controls/uptime_security.ts | 4 +- .../uptime/feature_controls/uptime_spaces.ts | 3 +- .../feature_controls/visualize_security.ts | 4 +- .../feature_controls/visualize_spaces.ts | 9 +- .../common/nav_links_builder.ts | 2 +- .../common/services/ui_capabilities.ts | 2 +- 109 files changed, 6419 insertions(+), 444 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-public.appbase.category.md create mode 100644 docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md create mode 100644 docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md create mode 100644 docs/development/core/public/kibana-plugin-public.appcategory.label.md create mode 100644 docs/development/core/public/kibana-plugin-public.appcategory.md create mode 100644 docs/development/core/public/kibana-plugin-public.appcategory.order.md create mode 100644 docs/development/core/public/kibana-plugin-public.chromenavlink.category.md create mode 100644 docs/development/core/public/kibana-plugin-public.legacynavlink.category.md create mode 100644 src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap create mode 100644 src/core/public/chrome/ui/header/header_logo.tsx create mode 100644 src/core/public/chrome/ui/header/nav_drawer.test.tsx create mode 100644 src/core/public/chrome/ui/header/nav_drawer.tsx create mode 100644 src/core/public/chrome/ui/header/nav_link.tsx create mode 100644 src/core/public/chrome/ui/header/recent_links.tsx create mode 100644 src/core/types/app_category.ts create mode 100644 src/core/utils/default_app_categories.ts rename test/functional/page_objects/{settings_page.js => settings_page.ts} (90%) diff --git a/docs/development/core/public/kibana-plugin-public.appbase.category.md b/docs/development/core/public/kibana-plugin-public.appbase.category.md new file mode 100644 index 00000000000000..215ebbbd0e1863 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [category](./kibana-plugin-public.appbase.category.md) + +## AppBase.category property + +The category definition of the product See [AppCategory](./kibana-plugin-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference + +Signature: + +```typescript +category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.md b/docs/development/core/public/kibana-plugin-public.appbase.md index eb6d91cb924888..6f547450b6a129 100644 --- a/docs/development/core/public/kibana-plugin-public.appbase.md +++ b/docs/development/core/public/kibana-plugin-public.appbase.md @@ -16,6 +16,7 @@ export interface AppBase | Property | Type | Description | | --- | --- | --- | | [capabilities](./kibana-plugin-public.appbase.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | +| [category](./kibana-plugin-public.appbase.category.md) | AppCategory | The category definition of the product See [AppCategory](./kibana-plugin-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference | | [chromeless](./kibana-plugin-public.appbase.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | | [euiIconType](./kibana-plugin-public.appbase.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [icon](./kibana-plugin-public.appbase.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md b/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md new file mode 100644 index 00000000000000..0245b548ae74f5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [ariaLabel](./kibana-plugin-public.appcategory.arialabel.md) + +## AppCategory.ariaLabel property + +If the visual label isn't appropriate for screen readers, can override it here + +Signature: + +```typescript +ariaLabel?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md b/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md new file mode 100644 index 00000000000000..90133735a00824 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [euiIconType](./kibana-plugin-public.appcategory.euiicontype.md) + +## AppCategory.euiIconType property + +Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined + +Signature: + +```typescript +euiIconType?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.label.md b/docs/development/core/public/kibana-plugin-public.appcategory.label.md new file mode 100644 index 00000000000000..171b1627f9ef8a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.label.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [label](./kibana-plugin-public.appcategory.label.md) + +## AppCategory.label property + +Label used for cateogry name. Also used as aria-label if one isn't set. + +Signature: + +```typescript +label: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.md b/docs/development/core/public/kibana-plugin-public.appcategory.md new file mode 100644 index 00000000000000..f1085e73252721 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) + +## AppCategory interface + +A category definition for nav links to know where to sort them in the left hand nav + +Signature: + +```typescript +export interface AppCategory +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [ariaLabel](./kibana-plugin-public.appcategory.arialabel.md) | string | If the visual label isn't appropriate for screen readers, can override it here | +| [euiIconType](./kibana-plugin-public.appcategory.euiicontype.md) | string | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined | +| [label](./kibana-plugin-public.appcategory.label.md) | string | Label used for cateogry name. Also used as aria-label if one isn't set. | +| [order](./kibana-plugin-public.appcategory.order.md) | number | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) | + diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.order.md b/docs/development/core/public/kibana-plugin-public.appcategory.order.md new file mode 100644 index 00000000000000..ef17ac04b78d6a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.order.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [order](./kibana-plugin-public.appcategory.order.md) + +## AppCategory.order property + +The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) + +Signature: + +```typescript +order?: number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md new file mode 100644 index 00000000000000..19d5a43a293079 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [category](./kibana-plugin-public.chromenavlink.category.md) + +## ChromeNavLink.category property + +The category the app lives in + +Signature: + +```typescript +readonly category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.md index 4cb9080222ac54..2afd6ce2d58c46 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.md @@ -17,6 +17,7 @@ export interface ChromeNavLink | --- | --- | --- | | [active](./kibana-plugin-public.chromenavlink.active.md) | boolean | Indicates whether or not this app is currently on the screen. | | [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md) | string | The base route used to open the root of an application. | +| [category](./kibana-plugin-public.chromenavlink.category.md) | AppCategory | The category the app lives in | | [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | boolean | Disables a link from being clickable. | | [euiIconType](./kibana-plugin-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | boolean | Hides a link from the navigation. | diff --git a/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md b/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md new file mode 100644 index 00000000000000..7026e9b519cc03 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [category](./kibana-plugin-public.legacynavlink.category.md) + +## LegacyNavLink.category property + +Signature: + +```typescript +category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.legacynavlink.md b/docs/development/core/public/kibana-plugin-public.legacynavlink.md index fc0c445f517b3b..e112110dd10f85 100644 --- a/docs/development/core/public/kibana-plugin-public.legacynavlink.md +++ b/docs/development/core/public/kibana-plugin-public.legacynavlink.md @@ -15,6 +15,7 @@ export interface LegacyNavLink | Property | Type | Description | | --- | --- | --- | +| [category](./kibana-plugin-public.legacynavlink.category.md) | AppCategory | | | [euiIconType](./kibana-plugin-public.legacynavlink.euiicontype.md) | string | | | [icon](./kibana-plugin-public.legacynavlink.icon.md) | string | | | [id](./kibana-plugin-public.legacynavlink.id.md) | string | | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 27037d46926c18..52aca7501e64dd 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -32,6 +32,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | | [AppBase](./kibana-plugin-public.appbase.md) | | +| [AppCategory](./kibana-plugin-public.appcategory.md) | A category definition for nav links to know where to sort them in the left hand nav | | [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | | [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | | [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | diff --git a/packages/kbn-i18n/src/loader.ts b/packages/kbn-i18n/src/loader.ts index 2d68079735c032..21f540f588f46f 100644 --- a/packages/kbn-i18n/src/loader.ts +++ b/packages/kbn-i18n/src/loader.ts @@ -17,15 +17,13 @@ * under the License. */ -import { readFile } from 'fs'; +import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; import { unique } from './core/helper'; import { Translation } from './translation'; -const asyncReadFile = promisify(readFile); - const TRANSLATION_FILE_EXTENSION = '.json'; /** @@ -69,7 +67,8 @@ function getLocaleFromFileName(fullFileName: string) { * @returns */ async function loadFile(pathToFile: string): Promise { - return JSON.parse(await asyncReadFile(pathToFile, 'utf8')); + // doing this at the moment because fs is mocked in a lot of places where this would otherwise fail + return JSON.parse(await promisify(fs.readFile)(pathToFile, 'utf8')); } /** diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 0d955482d2226c..63e542b0127ed1 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -31,6 +31,7 @@ import { PluginOpaqueId } from '../plugins'; import { IUiSettingsClient } from '../ui_settings'; import { RecursiveReadonly } from '../../utils'; import { SavedObjectsStart } from '../saved_objects'; +import { AppCategory } from '../../types'; /** @public */ export interface AppBase { @@ -44,6 +45,13 @@ export interface AppBase { */ title: string; + /** + * The category definition of the product + * See {@link AppCategory} + * See DEFAULT_APP_CATEGORIES for more reference + */ + category?: AppCategory; + /** * The initial status of the application. * Defaulting to `accessible` diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index abd04722a49f20..9018b219736345 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -29,6 +29,7 @@ import { notificationServiceMock } from '../notifications/notifications_service. import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { ChromeService } from './chrome_service'; import { App } from '../application'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; class FakeApp implements App { public title = `${this.id} App`; @@ -51,6 +52,7 @@ function defaultStartDeps(availableApps?: App[]) { http: httpServiceMock.createStartContract(), injectedMetadata: injectedMetadataServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), }; if (availableApps) { diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 09ea1afe35766c..6ab9fe158742a6 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -38,7 +38,7 @@ import { LoadingIndicator, HeaderWrapper as Header } from './ui'; import { DocLinksStart } from '../doc_links'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; import { KIBANA_ASK_ELASTIC_LINK } from './constants'; - +import { IUiSettingsClient } from '../ui_settings'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed'; @@ -85,6 +85,7 @@ interface StartDeps { http: HttpStart; injectedMetadata: InjectedMetadataStart; notifications: NotificationsStart; + uiSettings: IUiSettingsClient; } /** @internal */ @@ -139,6 +140,7 @@ export class ChromeService { http, injectedMetadata, notifications, + uiSettings, }: StartDeps): Promise { this.initVisibility(application); @@ -173,7 +175,6 @@ export class ChromeService { getHeaderComponent: () => ( -
), diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index 3b16c030ddcc93..4d3a1e9ecd1991 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -18,6 +18,7 @@ */ import { pick } from '../../../utils'; +import { AppCategory } from '../../'; /** * @public @@ -33,6 +34,11 @@ export interface ChromeNavLink { */ readonly title: string; + /** + * The category the app lives in + */ + readonly category?: AppCategory; + /** * The base route used to open the root of an application. */ diff --git a/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap new file mode 100644 index 00000000000000..0ebc44ba67862a --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap @@ -0,0 +1,5224 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NavDrawer Advanced setting set to grouped renders grouped items 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to grouped renders individual items if there are less than 7 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to grouped renders individual items if there is only 1 category 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to individual renders individual items 1`] = ` + + + + + + + +`; diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index d05a6bb53405c5..c3cefd180b16f6 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -17,141 +17,40 @@ * under the License. */ -import Url from 'url'; - -import React, { Component, createRef } from 'react'; -import * as Rx from 'rxjs'; - import { - // TODO: add type annotations EuiHeader, - EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem, EuiHeaderSectionItemButton, - EuiHorizontalRule, EuiIcon, - EuiImage, // @ts-ignore EuiNavDrawer, // @ts-ignore - EuiNavDrawerGroup, - // @ts-ignore EuiShowFor, } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; - -import { HeaderBadge } from './header_badge'; -import { HeaderBreadcrumbs } from './header_breadcrumbs'; -import { HeaderHelpMenu } from './header_help_menu'; -import { HeaderNavControls } from './header_nav_controls'; - +import React, { Component, createRef } from 'react'; +import * as Rx from 'rxjs'; import { ChromeBadge, ChromeBreadcrumb, + ChromeNavControl, ChromeNavLink, ChromeRecentlyAccessedHistoryItem, - ChromeNavControl, } from '../..'; +import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { InternalApplicationStart } from '../../../application/types'; - -// Providing a buffer between the limit and the cut off index -// protects from truncating just the last couple (6) characters -const TRUNCATE_LIMIT: number = 64; -const TRUNCATE_AT: number = 58; - -/** - * - * @param {string} url - a relative or root relative url. If a relative path is given then the - * absolute url returned will depend on the current page where this function is called from. For example - * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get - * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that - * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". - * @return {string} the relative url transformed into an absolute url - */ -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} - -function extendRecentlyAccessedHistoryItem( - navLinks: ChromeNavLink[], - recentlyAccessed: ChromeRecentlyAccessedHistoryItem, - basePath: HttpStart['basePath'] -) { - const href = relativeToAbsolute(basePath.prepend(recentlyAccessed.link)); - const navLink = navLinks.find(nl => href.startsWith(nl.subUrlBase || nl.baseUrl)); - - let titleAndAriaLabel = recentlyAccessed.label; - if (navLink) { - const objectTypeForAriaAppendix = navLink.title; - titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', { - defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}', - values: { - recentlyAccessedItemLinklabel: recentlyAccessed.label, - pageType: objectTypeForAriaAppendix, - }, - }); - } - - return { - ...recentlyAccessed, - href, - euiIconType: navLink ? navLink.euiIconType : undefined, - title: titleAndAriaLabel, - }; -} - -function extendNavLink(navLink: ChromeNavLink) { - if (navLink.legacy) { - return { - ...navLink, - href: navLink.url && !navLink.active ? navLink.url : navLink.baseUrl, - }; - } - - return { - ...navLink, - href: navLink.baseUrl, - }; -} - -function isModifiedEvent(event: MouseEvent) { - return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); -} - -function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { - let current = element; - while (current) { - if (current.tagName === 'A') { - return current as HTMLAnchorElement; - } - - if (!current.parentElement || current.parentElement === document.body) { - return undefined; - } - - current = current.parentElement; - } -} - -function truncateRecentItemLabel(label: string): string { - if (label.length > TRUNCATE_LIMIT) { - label = `${label.substring(0, TRUNCATE_AT)}…`; - } - - return label; -} - -export type HeaderProps = Pick>; +import { HeaderBadge } from './header_badge'; +import { NavSetting, OnIsLockedUpdate } from './'; +import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { HeaderHelpMenu } from './header_help_menu'; +import { HeaderNavControls } from './header_nav_controls'; +import { euiNavLink } from './nav_link'; +import { HeaderLogo } from './header_logo'; +import { NavDrawer } from './nav_drawer'; -interface Props { +export interface HeaderProps { kibanaVersion: string; application: InternalApplicationStart; appTitle$: Rx.Observable; @@ -168,28 +67,29 @@ interface Props { legacyMode: boolean; navControlsLeft$: Rx.Observable; navControlsRight$: Rx.Observable; - intl: InjectedIntl; basePath: HttpStart['basePath']; isLocked?: boolean; - onIsLockedUpdate?: (isLocked: boolean) => void; + navSetting$: Rx.Observable; + onIsLockedUpdate?: OnIsLockedUpdate; } interface State { appTitle: string; - currentAppId?: string; isVisible: boolean; - navLinks: ReadonlyArray>; - recentlyAccessed: ReadonlyArray>; + navLinks: ChromeNavLink[]; + recentlyAccessed: ChromeRecentlyAccessedHistoryItem[]; forceNavigation: boolean; navControlsLeft: readonly ChromeNavControl[]; navControlsRight: readonly ChromeNavControl[]; + navSetting: NavSetting; + currentAppId: string | undefined; } -class HeaderUI extends Component { +export class Header extends Component { private subscription?: Rx.Subscription; private navDrawerRef = createRef(); - constructor(props: Props) { + constructor(props: HeaderProps) { super(props); this.state = { @@ -200,6 +100,8 @@ class HeaderUI extends Component { forceNavigation: false, navControlsLeft: [], navControlsRight: [], + navSetting: 'grouped', + currentAppId: '', }; } @@ -214,7 +116,8 @@ class HeaderUI extends Component { Rx.combineLatest( this.props.navControlsLeft$, this.props.navControlsRight$, - this.props.application.currentAppId$ + this.props.application.currentAppId$, + this.props.navSetting$ ) ).subscribe({ next: ([ @@ -223,18 +126,17 @@ class HeaderUI extends Component { forceNavigation, navLinks, recentlyAccessed, - [navControlsLeft, navControlsRight, currentAppId], + [navControlsLeft, navControlsRight, currentAppId, navSetting], ]) => { this.setState({ appTitle, isVisible, forceNavigation, - navLinks: navLinks.map(extendNavLink), - recentlyAccessed: recentlyAccessed.map(ra => - extendRecentlyAccessedHistoryItem(navLinks, ra, this.props.basePath) - ), + navLinks: navLinks.filter(navLink => !navLink.hidden), + recentlyAccessed, navControlsLeft, navControlsRight, + navSetting, currentAppId, }); }, @@ -247,26 +149,12 @@ class HeaderUI extends Component { } } - public renderLogo() { - const { homeHref, intl } = this.props; - return ( - - ); - } - public renderMenuTrigger() { return ( this.navDrawerRef.current.toggleOpen()} > @@ -275,98 +163,29 @@ class HeaderUI extends Component { } public render() { + const { appTitle, isVisible, navControlsLeft, navControlsRight } = this.state; const { - application, badge$, - basePath, breadcrumbs$, helpExtension$, helpSupportUrl$, - intl, - isLocked, kibanaDocLink, kibanaVersion, - onIsLockedUpdate, - legacyMode, } = this.props; - const { - appTitle, - currentAppId, - isVisible, - navControlsLeft, - navControlsRight, - navLinks, - recentlyAccessed, - } = this.state; + const navLinks = this.state.navLinks.map(link => + euiNavLink( + link, + this.props.legacyMode, + this.state.currentAppId, + this.props.basePath, + this.props.application.navigateToApp + ) + ); if (!isVisible) { return null; } - const navLinksArray = navLinks - .filter(navLink => !navLink.hidden) - .map(navLink => ({ - key: navLink.id, - label: navLink.tooltip ?? navLink.title, - - // Use href and onClick to support "open in new tab" and SPA navigation in the same link - href: navLink.href, - onClick: (event: MouseEvent) => { - if ( - !legacyMode && // ignore when in legacy mode - !navLink.legacy && // ignore links to legacy apps - !event.defaultPrevented && // onClick prevented default - event.button === 0 && // ignore everything but left clicks - !isModifiedEvent(event) // ignore clicks with modifier keys - ) { - event.preventDefault(); - application.navigateToApp(navLink.id); - } - }, - - // Legacy apps use `active` property, NP apps should match the current app - isActive: navLink.active || currentAppId === navLink.id, - isDisabled: navLink.disabled, - - iconType: navLink.euiIconType, - icon: - !navLink.euiIconType && navLink.icon ? ( - - ) : ( - undefined - ), - 'data-test-subj': 'navDrawerAppsMenuLink', - })); - - const recentLinksArray = [ - { - label: intl.formatMessage({ - id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsLabel', - defaultMessage: 'Recently viewed', - }), - iconType: 'clock', - isDisabled: recentlyAccessed.length > 0 ? false : true, - flyoutMenu: { - title: intl.formatMessage({ - id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle', - defaultMessage: 'Recent items', - }), - listItems: recentlyAccessed.map(item => ({ - label: truncateRecentItemLabel(item.label), - title: item.title, - 'aria-label': item.title, - href: item.href, - iconType: item.euiIconType, - })), - }, - }, - ]; - return (
@@ -375,7 +194,13 @@ class HeaderUI extends Component { {this.renderMenuTrigger()} - {this.renderLogo()} + + + @@ -399,75 +224,17 @@ class HeaderUI extends Component { - - - - - - + />
); } - - private onNavClick = (event: React.MouseEvent) => { - const anchor = findClosestAnchor((event as any).nativeEvent.target); - if (!anchor) { - return; - } - - const navLink = this.state.navLinks.find(item => item.href === anchor.href); - if (navLink && navLink.disabled) { - event.preventDefault(); - return; - } - - if ( - !this.state.forceNavigation || - event.isDefaultPrevented() || - event.altKey || - event.metaKey || - event.ctrlKey - ) { - return; - } - - const toParsed = Url.parse(anchor.href); - const fromParsed = Url.parse(document.location.href); - const sameProto = toParsed.protocol === fromParsed.protocol; - const sameHost = toParsed.host === fromParsed.host; - const samePath = toParsed.path === fromParsed.path; - - if (sameProto && sameHost && samePath) { - if (toParsed.hash) { - document.location.reload(); - } - - // event.preventDefault() keeps the browser from seeing the new url as an update - // and even setting window.location does not mimic that behavior, so instead - // we use stopPropagation() to prevent angular from seeing the click and - // starting a digest cycle/attempting to handle it in the router. - event.stopPropagation(); - } - }; } - -export const Header = injectI18n(HeaderUI); diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx new file mode 100644 index 00000000000000..793b8646dabf78 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Url from 'url'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiHeaderLogo } from '@elastic/eui'; +import { NavLink } from './nav_link'; + +function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { + let current = element; + while (current) { + if (current.tagName === 'A') { + return current as HTMLAnchorElement; + } + + if (!current.parentElement || current.parentElement === document.body) { + return undefined; + } + + current = current.parentElement; + } +} + +function onClick( + event: React.MouseEvent, + forceNavigation: boolean, + navLinks: NavLink[] +) { + const anchor = findClosestAnchor((event as any).nativeEvent.target); + if (!anchor) { + return; + } + + const navLink = navLinks.find(item => item.href === anchor.href); + if (navLink && navLink.isDisabled) { + event.preventDefault(); + return; + } + + if ( + !forceNavigation || + event.isDefaultPrevented() || + event.altKey || + event.metaKey || + event.ctrlKey + ) { + return; + } + + const toParsed = Url.parse(anchor.href); + const fromParsed = Url.parse(document.location.href); + const sameProto = toParsed.protocol === fromParsed.protocol; + const sameHost = toParsed.host === fromParsed.host; + const samePath = toParsed.path === fromParsed.path; + + if (sameProto && sameHost && samePath) { + if (toParsed.hash) { + document.location.reload(); + } + + // event.preventDefault() keeps the browser from seeing the new url as an update + // and even setting window.location does not mimic that behavior, so instead + // we use stopPropagation() to prevent angular from seeing the click and + // starting a digest cycle/attempting to handle it in the router. + event.stopPropagation(); + } +} + +interface Props { + href: string; + navLinks: NavLink[]; + forceNavigation: boolean; +} + +export function HeaderLogo({ href, forceNavigation, navLinks }: Props) { + return ( + onClick(e, forceNavigation, navLinks)} + href={href} + aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { + defaultMessage: 'Go to home page', + })} + /> + ); +} diff --git a/src/core/public/chrome/ui/header/index.ts b/src/core/public/chrome/ui/header/index.ts index 6d59fc6d9433b8..b396c94b3f2a3e 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -26,3 +26,5 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './header_help_menu'; +export type NavSetting = 'grouped' | 'individual'; +export type OnIsLockedUpdate = (isLocked: boolean) => void; diff --git a/src/core/public/chrome/ui/header/nav_drawer.test.tsx b/src/core/public/chrome/ui/header/nav_drawer.test.tsx new file mode 100644 index 00000000000000..7272935b93a520 --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_drawer.test.tsx @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { cloneDeep } from 'lodash'; +import { mount } from 'enzyme'; +import React from 'react'; +import { NavSetting } from './'; +import { ChromeNavLink } from '../../../'; +import { AppCategory } from 'src/core/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../utils'; +import { NavDrawer } from './nav_drawer'; +import { euiNavLink } from './nav_link'; + +const { analyze, management, observability, security } = DEFAULT_APP_CATEGORIES; +const mockIBasePath = { + get: () => '/app', + prepend: () => '/app', + remove: () => '/app', +}; + +const getMockProps = (chromeNavLinks: ChromeNavLink[], navSetting: NavSetting = 'grouped') => ({ + navSetting, + navLinks: chromeNavLinks.map(link => + euiNavLink(link, true, undefined, mockIBasePath, () => Promise.resolve()) + ), + chromeNavLinks, + recentlyAccessedItems: [], + basePath: mockIBasePath, +}); + +const makeLink = (id: string, order: number, category?: AppCategory) => ({ + id, + category, + order, + title: id, + baseUrl: `http://localhost:5601/app/${id}`, + legacy: true, +}); + +const getMockChromeNavLink = () => + cloneDeep([ + makeLink('discover', 100, analyze), + makeLink('siem', 500, security), + makeLink('metrics', 600, observability), + makeLink('monitoring', 800, management), + makeLink('visualize', 200, analyze), + makeLink('dashboard', 300, analyze), + makeLink('canvas', 400, { label: 'customCategory' }), + makeLink('logs', 700, observability), + ]); + +describe('NavDrawer', () => { + describe('Advanced setting set to individual', () => { + it('renders individual items', () => { + const component = mount( + + ); + expect(component).toMatchSnapshot(); + }); + }); + describe('Advanced setting set to grouped', () => { + it('renders individual items if there are less than 7', () => { + const links = getMockChromeNavLink().slice(0, 5); + const component = mount(); + expect(component).toMatchSnapshot(); + }); + it('renders individual items if there is only 1 category', () => { + // management doesn't count as a category + const navLinks = [ + makeLink('discover', 100, analyze), + makeLink('siem', 500, analyze), + makeLink('metrics', 600, analyze), + makeLink('monitoring', 800, analyze), + makeLink('visualize', 200, analyze), + makeLink('dashboard', 300, management), + makeLink('canvas', 400, management), + makeLink('logs', 700, management), + ]; + const component = mount(); + expect(component).toMatchSnapshot(); + }); + it('renders grouped items', () => { + const component = mount(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx new file mode 100644 index 00000000000000..dbb68d5dd3901e --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -0,0 +1,170 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { groupBy, sortBy } from 'lodash'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui'; +import { NavSetting, OnIsLockedUpdate } from './'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..'; +import { AppCategory } from '../../../../types'; +import { HttpStart } from '../../../http'; +import { NavLink } from './nav_link'; +import { RecentLinks } from './recent_links'; + +function getAllCategories(allCategorizedLinks: Record) { + const allCategories = {} as Record; + + for (const [key, value] of Object.entries(allCategorizedLinks)) { + allCategories[key] = value[0].category; + } + + return allCategories; +} + +function getOrderedCategories( + mainCategories: Record, + categoryDictionary: ReturnType +) { + return sortBy( + Object.keys(mainCategories), + categoryName => categoryDictionary[categoryName]?.order + ); +} + +export interface Props { + navSetting: NavSetting; + isLocked?: boolean; + onIsLockedUpdate?: OnIsLockedUpdate; + navLinks: NavLink[]; + chromeNavLinks: ChromeNavLink[]; + recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[]; + basePath: HttpStart['basePath']; +} + +function navDrawerRenderer( + { + navSetting, + isLocked, + onIsLockedUpdate, + navLinks, + chromeNavLinks, + recentlyAccessedItems, + basePath, + }: Props, + ref: React.Ref +) { + const disableGroupedNavSetting = navSetting === 'individual'; + const groupedNavLinks = groupBy(navLinks, link => link?.category?.label); + const { undefined: unknowns, ...allCategorizedLinks } = groupedNavLinks; + const { Management: management, ...mainCategories } = allCategorizedLinks; + const categoryDictionary = getAllCategories(allCategorizedLinks); + const orderedCategories = getOrderedCategories(mainCategories, categoryDictionary); + const showUngroupedNav = + disableGroupedNavSetting || navLinks.length < 7 || Object.keys(mainCategories).length === 1; + + return ( + + {RecentLinks({ + recentlyAccessedItems, + navLinks: chromeNavLinks, + basePath, + })} + + {showUngroupedNav ? ( + + ) : ( + <> + { + const category = categoryDictionary[categoryName]!; + const links = mainCategories[categoryName]; + + if (links.length === 1) { + return { + ...links[0], + label: category.label, + iconType: category.euiIconType || links[0].iconType, + }; + } + + return { + 'data-test-subj': 'navDrawerCategory', + iconType: category.euiIconType, + label: category.label, + flyoutMenu: { + title: category.label, + listItems: sortBy(links, 'order').map(link => { + link['data-test-subj'] = 'navDrawerFlyoutLink'; + return link; + }), + }, + }; + }), + ...sortBy(unknowns, 'order'), + ]} + /> + + { + link['data-test-subj'] = 'navDrawerFlyoutLink'; + return link; + }), + }, + }, + ]} + /> + + )} + + ); +} + +export const NavDrawer = React.forwardRef(navDrawerRenderer); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx new file mode 100644 index 00000000000000..52b59c53b658c0 --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiImage } from '@elastic/eui'; +import { ChromeNavLink, CoreStart } from '../../../'; +import { HttpStart } from '../../../http'; + +function isModifiedEvent(event: MouseEvent) { + return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); +} + +function LinkIcon({ url }: { url: string }) { + return ; +} + +export type NavLink = ReturnType; + +export function euiNavLink( + navLink: ChromeNavLink, + legacyMode: boolean, + currentAppId: string | undefined, + basePath: HttpStart['basePath'], + navigateToApp: CoreStart['application']['navigateToApp'] +) { + const { + legacy, + url, + active, + baseUrl, + id, + title, + disabled, + euiIconType, + icon, + category, + order, + tooltip, + } = navLink; + let href = navLink.baseUrl; + + if (legacy) { + href = url && !active ? url : baseUrl; + } + + return { + category, + key: id, + label: tooltip ?? title, + href, // Use href and onClick to support "open in new tab" and SPA navigation in the same link + onClick(event: MouseEvent) { + if ( + !legacyMode && // ignore when in legacy mode + !legacy && // ignore links to legacy apps + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore everything but left clicks + !isModifiedEvent(event) // ignore clicks with modifier keys + ) { + event.preventDefault(); + navigateToApp(navLink.id); + } + }, + // Legacy apps use `active` property, NP apps should match the current app + isActive: active || currentAppId === id, + isDisabled: disabled, + iconType: euiIconType, + icon: !euiIconType && icon ? : undefined, + order, + 'data-test-subj': 'navDrawerAppsMenuLink', + }; +} diff --git a/src/core/public/chrome/ui/header/recent_links.tsx b/src/core/public/chrome/ui/header/recent_links.tsx new file mode 100644 index 00000000000000..a947ab1c450563 --- /dev/null +++ b/src/core/public/chrome/ui/header/recent_links.tsx @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { EuiNavDrawerGroup } from '@elastic/eui'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..'; +import { HttpStart } from '../../../http'; + +// Providing a buffer between the limit and the cut off index +// protects from truncating just the last couple (6) characters +const TRUNCATE_LIMIT: number = 64; +const TRUNCATE_AT: number = 58; + +export function truncateRecentItemLabel(label: string): string { + if (label.length > TRUNCATE_LIMIT) { + label = `${label.substring(0, TRUNCATE_AT)}…`; + } + + return label; +} + +/** + * @param {string} url - a relative or root relative url. If a relative path is given then the + * absolute url returned will depend on the current page where this function is called from. For example + * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get + * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that + * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". + * @return {string} the relative url transformed into an absolute url + */ +function relativeToAbsolute(url: string) { + const a = document.createElement('a'); + a.setAttribute('href', url); + return a.href; +} + +function prepareForEUI( + recentlyAccessed: ChromeRecentlyAccessedHistoryItem[], + navLinks: ChromeNavLink[], + basePath: HttpStart['basePath'] +) { + return recentlyAccessed.map(({ link, label }) => { + const href = relativeToAbsolute(basePath.prepend(link)); + const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + let titleAndAriaLabel = label; + + if (navLink) { + titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', { + defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}', + values: { + recentlyAccessedItemLinklabel: label, + pageType: navLink.title, + }, + }); + } + + return { + href, + label: truncateRecentItemLabel(label), + title: titleAndAriaLabel, + 'aria-label': titleAndAriaLabel, + iconType: navLink?.euiIconType, + }; + }); +} + +interface Props { + recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[]; + navLinks: ChromeNavLink[]; + basePath: HttpStart['basePath']; +} + +export function RecentLinks({ recentlyAccessedItems, navLinks, basePath }: Props) { + return ( + + ); +} diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index be69cacdd271a7..5fb12ec1549521 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -239,6 +239,7 @@ export class CoreSystem { http, injectedMetadata, notifications, + uiSettings, }); application.registerMountContext(this.coreContext.coreId, 'core', () => ({ diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 5e732dd05e6168..bf8cab9a3c7787 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -77,7 +77,8 @@ import { } from './context'; export { CoreContext, CoreSystem } from './core_system'; -export { RecursiveReadonly } from '../utils'; +export { RecursiveReadonly, DEFAULT_APP_CATEGORIES } from '../utils'; +export { AppCategory } from '../types'; export { ApplicationSetup, diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 0bde1b68e1876a..1075a7741ee324 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -26,10 +26,12 @@ import { UserProvidedValues, } from '../../server/types'; import { deepFreeze } from '../../utils/'; +import { AppCategory } from '../'; /** @public */ export interface LegacyNavLink { id: string; + category?: AppCategory; title: string; order: number; url: string; @@ -52,6 +54,7 @@ export interface InjectedMetadataParams { buildNumber: number; branch: string; basePath: string; + category?: AppCategory; csp: { warnLegacyBrowsers: boolean; }; @@ -75,6 +78,7 @@ export interface InjectedMetadataParams { basePath: string; serverName: string; devMode: boolean; + category?: AppCategory; uiSettings: { defaults: Record; user?: Record; diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index f906aff1759e2e..cc3210771eecc2 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -74,6 +74,7 @@ export class LegacyPlatformService { appUrl: navLink.url, subUrlBase: navLink.subUrlBase, linkToLastSubUrl: navLink.linkToLastSubUrl, + category: navLink.category, }) ); diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f7b260c68ee96b..0da6e0d422f2d7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -26,6 +26,7 @@ export interface App extends AppBase { // @public (undocumented) export interface AppBase { capabilities?: Partial; + category?: AppCategory; chromeless?: boolean; euiIconType?: string; icon?: string; @@ -40,6 +41,14 @@ export interface AppBase { updater$?: Observable; } +// @public +export interface AppCategory { + ariaLabel?: string; + euiIconType?: string; + label: string; + order?: number; +} + // @public export type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; @@ -251,6 +260,7 @@ export interface ChromeNavLink { // @deprecated readonly active?: boolean; readonly baseUrl: string; + readonly category?: AppCategory; // @deprecated readonly disabled?: boolean; readonly euiIconType?: string; @@ -410,6 +420,26 @@ export class CoreSystem { stop(): void; } +// @internal (undocumented) +export const DEFAULT_APP_CATEGORIES: Readonly<{ + analyze: { + label: string; + order: number; + }; + observability: { + label: string; + order: number; + }; + security: { + label: string; + order: number; + }; + management: { + label: string; + euiIconType: string; + }; +}>; + // @public (undocumented) export interface DocLinksStart { // (undocumented) @@ -746,6 +776,8 @@ export interface LegacyCoreStart extends CoreStart { // @public (undocumented) export interface LegacyNavLink { + // (undocumented) + category?: AppCategory; // (undocumented) euiIconType?: string; // (undocumented) diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index 9867274d224bd0..a19133c30659b0 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -65,6 +65,7 @@ function getUiAppsNavLinks({ uiAppSpecs = [] }: LegacyUiExports, pluginSpecs: Le return { id, + category: spec.category, title: spec.title, order: typeof spec.order === 'number' ? spec.order : 0, icon: spec.icon, @@ -79,6 +80,7 @@ function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[] return (uiExports.navLinkSpecs || []) .map(spec => ({ id: spec.id, + category: spec.category, title: spec.title, order: typeof spec.order === 'number' ? spec.order : 0, url: spec.url, diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 40b8244a318903..d51058ca561c6b 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -139,7 +139,7 @@ export type LegacyNavLinkSpec = Record & ChromeNavLink; */ export type LegacyAppSpec = Pick< ChromeNavLink, - 'title' | 'order' | 'icon' | 'euiIconType' | 'url' | 'linkToLastSubUrl' | 'hidden' + 'title' | 'order' | 'icon' | 'euiIconType' | 'url' | 'linkToLastSubUrl' | 'hidden' | 'category' > & { pluginId?: string; id?: string; listed?: boolean }; /** diff --git a/src/core/types/app_category.ts b/src/core/types/app_category.ts new file mode 100644 index 00000000000000..83a3693f009b6c --- /dev/null +++ b/src/core/types/app_category.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** @public */ + +/** + * A category definition for nav links to know where to sort them in the left hand nav + * @public + */ +export interface AppCategory { + /** + * Label used for cateogry name. + * Also used as aria-label if one isn't set. + */ + label: string; + + /** + * If the visual label isn't appropriate for screen readers, + * can override it here + */ + ariaLabel?: string; + + /** + * The order that categories will be sorted in + * Prefer large steps between categories to allow for further editing + * (Default categories are in steps of 1000) + */ + order?: number; + + /** + * Define an icon to be used for the category + * If the category is only 1 item, and no icon is defined, will default to the product icon + * Defaults to initials if no icon is defined + */ + euiIconType?: string; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index d01b514c770a77..7ddb6b0d8dfbbc 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -23,3 +23,4 @@ */ export * from './core_service'; export * from './capabilities'; +export * from './app_category'; diff --git a/src/core/utils/default_app_categories.ts b/src/core/utils/default_app_categories.ts new file mode 100644 index 00000000000000..3e3cc2fef2a229 --- /dev/null +++ b/src/core/utils/default_app_categories.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +/** @internal */ +export const DEFAULT_APP_CATEGORIES = Object.freeze({ + analyze: { + label: i18n.translate('core.ui.analyzeNavList.label', { + defaultMessage: 'Analyze', + }), + order: 1000, + }, + observability: { + label: i18n.translate('core.ui.observabilityNavList.label', { + defaultMessage: 'Observability', + }), + order: 2000, + }, + security: { + label: i18n.translate('core.ui.securityNavList.label', { + defaultMessage: 'Security', + }), + order: 3000, + }, + management: { + label: i18n.translate('core.ui.managementNavList.label', { + defaultMessage: 'Management', + }), + euiIconType: 'managementApp', + }, +}); diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 7c8ed481c0a7d0..7317c222d3bc32 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -28,3 +28,4 @@ export * from './pick'; export * from './promise'; export * from './url'; export * from './unset'; +export * from './default_app_categories'; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 0366d8b27f2118..55bd8520502187 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -34,6 +34,7 @@ import { getUiSettingDefaults } from './ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; const mkdirAsync = promisify(Fs.mkdir); @@ -83,6 +84,7 @@ export default function(kibana) { order: -1003, url: `${kbnBaseUrl}#/discover`, euiIconType: 'discoverApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:visualize', @@ -92,6 +94,7 @@ export default function(kibana) { order: -1002, url: `${kbnBaseUrl}#/visualize`, euiIconType: 'visualizeApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:dashboard', @@ -107,6 +110,7 @@ export default function(kibana) { // to determine what url to use for the app link. subUrlBase: `${kbnBaseUrl}#/dashboard`, euiIconType: 'dashboardApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:dev_tools', @@ -116,16 +120,18 @@ export default function(kibana) { order: 9001, url: '/app/kibana#/dev_tools', euiIconType: 'devToolsApp', + category: DEFAULT_APP_CATEGORIES.management, }, { - id: 'kibana:management', + id: 'kibana:stack_management', title: i18n.translate('kbn.managementTitle', { - defaultMessage: 'Management', + defaultMessage: 'Stack Management', }), order: 9003, url: `${kbnBaseUrl}#/management`, euiIconType: 'managementApp', linkToLastSubUrl: false, + category: DEFAULT_APP_CATEGORIES.management, }, ], diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx index d2dda32f318fe5..1aad7e953b8de0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx @@ -39,7 +39,7 @@ const DiscoverFetchError = ({ fetchError }: Props) => { if (fetchError.lang === 'painless') { const { chrome } = getServices(); - const mangagementUrlObj = chrome.navLinks.get('kibana:management'); + const mangagementUrlObj = chrome.navLinks.get('kibana:stack_management'); const managementUrl = mangagementUrlObj ? mangagementUrlObj.url : ''; const url = `${managementUrl}/kibana/index_patterns`; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js index be2ceb66f69d03..27d4f1a8b1c1fa 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js @@ -129,8 +129,8 @@ describe('home', () => { test('should not render directory entry when showOnHomePage is false', async () => { const directoryEntry = { - id: 'management', - title: 'Management', + id: 'stack-management', + title: 'Stack Management', description: 'Your center console for managing the Elastic Stack.', icon: 'managementApp', path: 'management_landing_page', diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index d62770956b88ef..1305310b6f6151 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -74,7 +74,7 @@ export function updateLandingPage(version) {

@@ -93,7 +93,7 @@ export function updateLandingPage(version) {

@@ -173,11 +173,11 @@ uiModules.get('apps/management').directive('kbnManagementLanding', function(kbnV FeatureCatalogueRegistryProvider.register(() => { return { - id: 'management', - title: i18n.translate('kbn.management.managementLabel', { - defaultMessage: 'Management', + id: 'stack-management', + title: i18n.translate('kbn.stackManagement.managementLabel', { + defaultMessage: 'Stack Management', }), - description: i18n.translate('kbn.management.managementDescription', { + description: i18n.translate('kbn.stackManagement.managementDescription', { defaultMessage: 'Your center console for managing the Elastic Stack.', }), icon: 'managementApp', diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index dc8fee4a849c50..9b848666541ce7 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -1170,5 +1170,24 @@ export function getUiSettingDefaults() { category: ['accessibility'], requiresPageReload: true, }, + pageNavigation: { + name: i18n.translate('kbn.advancedSettings.pageNavigationName', { + defaultMessage: 'Side nav style', + }), + value: 'grouped', + description: i18n.translate('kbn.advancedSettings.pageNavigationDesc', { + defaultMessage: 'Change the style of navigation', + }), + type: 'select', + options: ['grouped', 'individual'], + optionLabels: { + grouped: i18n.translate('kbn.advancedSettings.pageNavigationGrouped', { + defaultMessage: 'Grouped', + }), + individual: i18n.translate('kbn.advancedSettings.pageNavigationIndividual', { + defaultMessage: 'Individual', + }), + }, + }, }; } diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts index 65601b53718151..4962c948f842f2 100644 --- a/src/legacy/core_plugins/management/index.ts +++ b/src/legacy/core_plugins/management/index.ts @@ -23,7 +23,7 @@ import { Legacy } from '../../../../kibana'; // eslint-disable-next-line import/no-default-export export default function ManagementPlugin(kibana: any) { const config: Legacy.PluginSpecOptions = { - id: 'management', + id: 'stack-management', publicDir: resolve(__dirname, 'public'), config: (Joi: any) => { return Joi.object({ diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts index cb4ff79969a322..cf2c9c883871bf 100644 --- a/src/legacy/core_plugins/telemetry/common/constants.ts +++ b/src/legacy/core_plugins/telemetry/common/constants.ts @@ -80,4 +80,4 @@ export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings'; * The type name used within the Monitoring index to publish management stats. * @type {string} */ -export const KIBANA_MANAGEMENT_STATS_TYPE = 'management'; +export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts index f45cf7fc6bb33f..44926b644ced5c 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts @@ -19,7 +19,7 @@ import { Server } from 'hapi'; import { size } from 'lodash'; -import { KIBANA_MANAGEMENT_STATS_TYPE } from '../../../common/constants'; +import { KIBANA_STACK_MANAGEMENT_STATS_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; import { SavedObjectsClient } from '../../../../../../core/server'; @@ -54,7 +54,7 @@ export function registerManagementUsageCollector( server: any ) { const collector = usageCollection.makeUsageCollector({ - type: KIBANA_MANAGEMENT_STATS_TYPE, + type: KIBANA_STACK_MANAGEMENT_STATS_TYPE, isReady: () => true, fetch: createCollectorFetch(server), }); diff --git a/src/legacy/core_plugins/timelion/index.ts b/src/legacy/core_plugins/timelion/index.ts index ec121647f4e477..d725327e2365b5 100644 --- a/src/legacy/core_plugins/timelion/index.ts +++ b/src/legacy/core_plugins/timelion/index.ts @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { LegacyPluginApi, LegacyPluginInitializer } from 'src/legacy/plugin_discovery/types'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../core/utils'; import { plugin } from './server'; import { CustomCoreSetup } from './server/plugin'; @@ -60,6 +61,7 @@ const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPl icon: 'plugins/timelion/icon.svg', euiIconType: 'timelionApp', main: 'plugins/timelion/app', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index fe886b9d178111..9425003eae8747 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -24,6 +24,7 @@ import { Capabilities } from '../../core/server'; import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsManagementDefinition } from '../../core/server/saved_objects/management'; +import { AppCategory } from '../../core/types'; /** * Usage @@ -53,6 +54,7 @@ export interface LegacyPluginOptions { uiExports: Partial<{ app: Partial<{ title: string; + category?: AppCategory; description: string; main: string; icon: string; diff --git a/src/legacy/ui/public/management/breadcrumbs.ts b/src/legacy/ui/public/management/breadcrumbs.ts index fe53bcfde9e1f8..936e99caff565f 100644 --- a/src/legacy/ui/public/management/breadcrumbs.ts +++ b/src/legacy/ui/public/management/breadcrumbs.ts @@ -20,8 +20,8 @@ import { i18n } from '@kbn/i18n'; export const MANAGEMENT_BREADCRUMB = Object.freeze({ - text: i18n.translate('common.ui.management.breadcrumb', { - defaultMessage: 'Management', + text: i18n.translate('common.ui.stackManagement.breadcrumb', { + defaultMessage: 'Stack Management', }), href: '#/management', }); diff --git a/src/legacy/ui/ui_apps/ui_app.js b/src/legacy/ui/ui_apps/ui_app.js index 9c82ff2abedb57..1cfd54588b516e 100644 --- a/src/legacy/ui/ui_apps/ui_app.js +++ b/src/legacy/ui/ui_apps/ui_app.js @@ -32,6 +32,7 @@ export class UiApp { hidden, linkToLastSubUrl, listed, + category, url = `/app/${id}`, } = spec; @@ -46,6 +47,7 @@ export class UiApp { this._icon = icon; this._euiIconType = euiIconType; this._linkToLastSubUrl = linkToLastSubUrl; + this._category = category; this._hidden = hidden; this._listed = listed; this._url = url; @@ -68,6 +70,7 @@ export class UiApp { euiIconType: this._euiIconType, url: this._url, linkToLastSubUrl: this._linkToLastSubUrl, + category: this._category, }); } } @@ -115,6 +118,7 @@ export class UiApp { main: this._main, navLink: this._navLink, linkToLastSubUrl: this._linkToLastSubUrl, + category: this._category, }; } } diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js b/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js index d7ac49d9d49a32..639a5a7c58e180 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js +++ b/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js @@ -34,6 +34,7 @@ function applySpecDefaults(spec, type, pluginSpec) { linkToLastSubUrl = true, listed = !hidden, url = `/app/${id}`, + category, } = spec; if (spec.injectVars) { @@ -61,6 +62,7 @@ function applySpecDefaults(spec, type, pluginSpec) { linkToLastSubUrl, listed, url, + category, }; } diff --git a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js b/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js index 37e023127ed419..543fe05b13e43e 100644 --- a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js +++ b/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js @@ -45,6 +45,7 @@ describe('UiNavLink', () => { euiIconType: spec.euiIconType, hidden: spec.hidden, disabled: spec.disabled, + category: undefined, // defaults linkToLastSubUrl: true, diff --git a/src/legacy/ui/ui_nav_links/ui_nav_link.js b/src/legacy/ui/ui_nav_links/ui_nav_link.js index 7537a60adbcf2d..5888c21a53c95c 100644 --- a/src/legacy/ui/ui_nav_links/ui_nav_link.js +++ b/src/legacy/ui/ui_nav_links/ui_nav_link.js @@ -31,6 +31,7 @@ export class UiNavLink { hidden = false, disabled = false, tooltip = '', + category, } = spec; this._id = id; @@ -44,6 +45,7 @@ export class UiNavLink { this._hidden = hidden; this._disabled = disabled; this._tooltip = tooltip; + this._category = category; } getOrder() { @@ -63,6 +65,7 @@ export class UiNavLink { hidden: this._hidden, disabled: this._disabled, tooltip: this._tooltip, + category: this._category, }; } } diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx index cb0b82d0f0bde4..69ba813d2347ed 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -161,14 +161,15 @@ export class ManagementSidebarNav extends React.Component< } public render() { - const HEADER_ID = 'management-nav-header'; + const HEADER_ID = 'stack-management-nav-header'; return ( <>

{i18n.translate('management.nav.label', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', })}

diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js index 63d919377f89ea..ca35db56c340be 100644 --- a/src/plugins/management/public/legacy/sections_register.js +++ b/src/plugins/management/public/legacy/sections_register.js @@ -27,7 +27,8 @@ export class LegacyManagementAdapter { 'management', { display: i18n.translate('management.displayName', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', }), }, capabilities @@ -35,6 +36,7 @@ export class LegacyManagementAdapter { this.main.register('data', { display: i18n.translate('management.connectDataDisplayName', { + // todo defaultMessage: 'Connect Data', }), order: 0, diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx index 705d98eaaf2ffd..02b3ea306c23d8 100644 --- a/src/plugins/management/public/management_app.tsx +++ b/src/plugins/management/public/management_app.tsx @@ -64,7 +64,8 @@ export class ManagementApp { coreStart.chrome.setBreadcrumbs([ { text: i18n.translate('management.breadcrumb', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', }), href: '#/management', }, diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index 90f02c36b3b7fa..0b628100a98bd6 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -34,6 +34,7 @@ export default function({ getService, getPageObjects }) { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + pageNavigation: 'individual', }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); @@ -83,7 +84,7 @@ export default function({ getService, getPageObjects }) { describe('is false', () => { before(async () => { - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs'); }); @@ -98,7 +99,7 @@ export default function({ getService, getPageObjects }) { }); after(async () => { - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs'); await PageObjects.header.clickDashboard(); diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.js index a32024adb5ec77..e685c43e9ce98a 100644 --- a/test/functional/apps/management/_index_pattern_filter.js +++ b/test/functional/apps/management/_index_pattern_filter.js @@ -27,7 +27,7 @@ export default function({ getService, getPageObjects }) { describe('index pattern filter', function describeIndexTests() { before(async function() { // delete .kibana index and then wait for Kibana to re-create it - await kibanaServer.uiSettings.replace({}); + await kibanaServer.uiSettings.replace({ pageNavigation: 'individual' }); await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); }); diff --git a/test/functional/apps/visualize/_lab_mode.js b/test/functional/apps/visualize/_lab_mode.js index 3ee806af8165db..b082480d95a2ee 100644 --- a/test/functional/apps/visualize/_lab_mode.js +++ b/test/functional/apps/visualize/_lab_mode.js @@ -23,7 +23,6 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const PageObjects = getPageObjects(['common', 'header', 'discover', 'settings']); - // Flaky: https://github.com/elastic/kibana/issues/19743 describe('visualize lab mode', () => { it('disabling does not break loading saved searches', async () => { await PageObjects.common.navigateToUrl('discover', ''); @@ -36,7 +35,7 @@ export default function({ getService, getPageObjects }) { log.info('found saved search before toggling enableLabs mode'); // Navigate to advanced setting and disable lab mode - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs'); @@ -50,7 +49,7 @@ export default function({ getService, getPageObjects }) { after(async () => { await PageObjects.discover.closeLoadSaveSearchPanel(); - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs'); }); diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js index f82e4e4387e270..05edd64545a565 100644 --- a/test/functional/page_objects/header_page.js +++ b/test/functional/page_objects/header_page.js @@ -59,8 +59,8 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await this.awaitGlobalLoadingIndicatorHidden(); } - async clickManagement() { - await appsMenu.clickLink('Management'); + async clickStackManagement() { + await appsMenu.clickLink('Stack Management'); await this.awaitGlobalLoadingIndicatorHidden(); } diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.ts similarity index 90% rename from test/functional/page_objects/settings_page.js rename to test/functional/page_objects/settings_page.ts index a4ae361b12ed83..e92780143f09a6 100644 --- a/test/functional/page_objects/settings_page.js +++ b/test/functional/page_objects/settings_page.ts @@ -19,8 +19,10 @@ import { map as mapAsync } from 'bluebird'; import expect from '@kbn/expect'; +import { NavSetting } from '../../../src/core/public/chrome/ui/header/'; +import { FtrProviderContext } from '../ftr_provider_context'; -export function SettingsPageProvider({ getService, getPageObjects }) { +export function SettingsPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const browser = getService('browser'); @@ -34,7 +36,8 @@ export function SettingsPageProvider({ getService, getPageObjects }) { async clickNavigation() { find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); } - async clickLinkText(text) { + + async clickLinkText(text: string) { await find.clickByDisplayedLinkText(text); } async clickKibanaSettings() { @@ -55,6 +58,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { // check for the index pattern info flyout that covers the // create index pattern button on smaller screens + // @ts-ignore await retry.waitFor('index pattern info flyout', async () => { if (await testSubjects.exists('CreateIndexPatternPrompt')) { await testSubjects.click('CreateIndexPatternPrompt > euiFlyoutCloseButton'); @@ -62,18 +66,18 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async getAdvancedSettings(propertyName) { + async getAdvancedSettings(propertyName: string) { log.debug('in getAdvancedSettings'); const setting = await testSubjects.find(`advancedSetting-editField-${propertyName}`); return await setting.getAttribute('value'); } - async expectDisabledAdvancedSetting(propertyName) { + async expectDisabledAdvancedSetting(propertyName: string) { const setting = await testSubjects.find(`advancedSetting-editField-${propertyName}`); expect(setting.getAttribute('disabled')).to.eql(''); } - async getAdvancedSettingCheckbox(propertyName) { + async getAdvancedSettingCheckbox(propertyName: string) { log.debug('in getAdvancedSettingCheckbox'); return await testSubjects.getAttribute( `advancedSetting-editField-${propertyName}`, @@ -81,12 +85,12 @@ export function SettingsPageProvider({ getService, getPageObjects }) { ); } - async clearAdvancedSettings(propertyName) { + async clearAdvancedSettings(propertyName: string) { await testSubjects.click(`advancedSetting-resetField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); } - async setAdvancedSettingsSelect(propertyName, propertyValue) { + async setAdvancedSettingsSelect(propertyName: string, propertyValue: string) { await find.clickByCssSelector( `[data-test-subj="advancedSetting-editField-${propertyName}"] option[value="${propertyValue}"]` ); @@ -95,7 +99,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async setAdvancedSettingsInput(propertyName, propertyValue) { + async setAdvancedSettingsInput(propertyName: string, propertyValue: string) { const input = await testSubjects.find(`advancedSetting-editField-${propertyName}`); await input.clearValue(); await input.type(propertyValue); @@ -103,7 +107,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async toggleAdvancedSettingCheckbox(propertyName) { + async toggleAdvancedSettingCheckbox(propertyName: string) { testSubjects.click(`advancedSetting-editField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.click(`advancedSetting-saveEditField-${propertyName}`); @@ -126,7 +130,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await testSubjects.find('createIndexPatternTimeFieldSelect'); } - async selectTimeFieldOption(selection) { + async selectTimeFieldOption(selection: string) { // open dropdown await this.clickTimeFieldNameField(); // close dropdown, keep focus @@ -141,7 +145,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async getTimeFieldOption(selection) { + async getTimeFieldOption(selection: string) { return await find.displayedByCssSelector('option[value="' + selection + '"]'); } @@ -174,9 +178,9 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await find.allByCssSelector('table.euiTable thead tr th'); } - async sortBy(columnName) { + async sortBy(columnName: string) { const chartTypes = await find.allByCssSelector('table.euiTable thead tr th button'); - async function getChartType(chart) { + async function getChartType(chart: Record) { const chartString = await chart.getVisibleText(); if (chartString === columnName) { await chart.click(); @@ -187,7 +191,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return Promise.all(getChartTypesPromises); } - async getTableRow(rowNumber, colNumber) { + async getTableRow(rowNumber: number, colNumber: number) { // passing in zero-based index, but adding 1 for css 1-based indexes return await find.byCssSelector( 'table.euiTable tbody tr:nth-child(' + @@ -234,13 +238,13 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async setFieldTypeFilter(type) { + async setFieldTypeFilter(type: string) { await find.clickByCssSelector( 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]' ); } - async setScriptedFieldLanguageFilter(language) { + async setScriptedFieldLanguageFilter(language: string) { await find.clickByCssSelector( 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + @@ -248,13 +252,13 @@ export function SettingsPageProvider({ getService, getPageObjects }) { ); } - async filterField(name) { + async filterField(name: string) { const input = await testSubjects.find('indexPatternFieldFilter'); await input.clearValue(); await input.type(name); } - async openControlsByName(name) { + async openControlsByName(name: string) { await this.filterField(name); const tableFields = await ( await find.byCssSelector( @@ -312,7 +316,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { } async createIndexPattern( - indexPatternName, + indexPatternName: string, timefield = '@timestamp', isStandardIndexPattern = true ) { @@ -364,7 +368,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { async getIndexPatternIdFromUrl() { const currentUrl = await browser.getCurrentUrl(); - const indexPatternId = currentUrl.match(/.*\/(.*)/)[1]; + const indexPatternId = currentUrl.match(/.*\/(.*)/)![1]; log.debug('index pattern ID: ', indexPatternId); @@ -423,12 +427,19 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await testSubjects.click('tab-sourceFilters'); } - async editScriptedField(name) { + async editScriptedField(name: string) { await this.filterField(name); await find.clickByCssSelector('.euiTableRowCell--hasActions button:first-child'); } - async addScriptedField(name, language, type, format, popularity, script) { + async addScriptedField( + name: string, + language: string, + type: string, + format: Record, + popularity: string, + script: string + ) { await this.clickAddScriptedField(); await this.setScriptedFieldName(name); if (language) await this.setScriptedFieldLanguage(language); @@ -469,42 +480,42 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async setScriptedFieldName(name) { + async setScriptedFieldName(name: string) { log.debug('set scripted field name = ' + name); const field = await testSubjects.find('editorFieldName'); await field.clearValue(); await field.type(name); } - async setScriptedFieldLanguage(language) { + async setScriptedFieldLanguage(language: string) { log.debug('set scripted field language = ' + language); await find.clickByCssSelector( 'select[data-test-subj="editorFieldLang"] > option[value="' + language + '"]' ); } - async setScriptedFieldType(type) { + async setScriptedFieldType(type: string) { log.debug('set scripted field type = ' + type); await find.clickByCssSelector( 'select[data-test-subj="editorFieldType"] > option[value="' + type + '"]' ); } - async setFieldFormat(format) { + async setFieldFormat(format: string) { log.debug('set scripted field format = ' + format); await find.clickByCssSelector( 'select[data-test-subj="editorSelectedFormatId"] > option[value="' + format + '"]' ); } - async setScriptedFieldUrlType(type) { + async setScriptedFieldUrlType(type: string) { log.debug('set scripted field Url type = ' + type); await find.clickByCssSelector( 'select[data-test-subj="urlEditorType"] > option[value="' + type + '"]' ); } - async setScriptedFieldUrlTemplate(template) { + async setScriptedFieldUrlTemplate(template: string) { log.debug('set scripted field Url Template = ' + template); const urlTemplateField = await find.byCssSelector( 'input[data-test-subj="urlEditorUrlTemplate"]' @@ -512,7 +523,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await urlTemplateField.type(template); } - async setScriptedFieldUrlLabelTemplate(labelTemplate) { + async setScriptedFieldUrlLabelTemplate(labelTemplate: string) { log.debug('set scripted field Url Label Template = ' + labelTemplate); const urlEditorLabelTemplate = await find.byCssSelector( 'input[data-test-subj="urlEditorLabelTemplate"]' @@ -520,7 +531,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await urlEditorLabelTemplate.type(labelTemplate); } - async setScriptedFieldDatePattern(datePattern) { + async setScriptedFieldDatePattern(datePattern: string) { log.debug('set scripted field Date Pattern = ' + datePattern); const datePatternField = await find.byCssSelector( 'input[data-test-subj="dateEditorPattern"]' @@ -531,21 +542,21 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await datePatternField.type(datePattern); } - async setScriptedFieldStringTransform(stringTransform) { + async setScriptedFieldStringTransform(stringTransform: string) { log.debug('set scripted field string Transform = ' + stringTransform); await find.clickByCssSelector( 'select[data-test-subj="stringEditorTransform"] > option[value="' + stringTransform + '"]' ); } - async setScriptedFieldPopularity(popularity) { + async setScriptedFieldPopularity(popularity: string) { log.debug('set scripted field popularity = ' + popularity); const field = await testSubjects.find('editorFieldCount'); await field.clearValue(); await field.type(popularity); } - async setScriptedFieldScript(script) { + async setScriptedFieldScript(script: string) { log.debug('set scripted field script = ' + script); const aceEditorCssSelector = '[data-test-subj="editorFieldScript"] .ace_editor'; await find.clickByCssSelector(aceEditorCssSelector); @@ -555,7 +566,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await browser.pressKeys(...script.split('')); } - async openScriptedFieldHelp(activeTab) { + async openScriptedFieldHelp(activeTab: string) { log.debug('open Scripted Fields help'); let isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); if (!isOpen) { @@ -577,7 +588,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await flyout.ensureClosed('scriptedFieldsHelpFlyout'); } - async executeScriptedField(script, additionalField) { + async executeScriptedField(script: string, additionalField: string) { log.debug('execute Scripted Fields help'); await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked await this.setScriptedFieldScript(script); @@ -595,7 +606,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return scriptResults; } - async importFile(path, overwriteAll = true) { + async importFile(path: string, overwriteAll = true) { log.debug(`importFile(${path})`); log.debug(`Clicking importObjects`); @@ -645,7 +656,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await testSubjects.click('importSavedObjectsConfirmBtn'); } - async associateIndexPattern(oldIndexPatternId, newIndexPatternTitle) { + async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) { await find.clickByCssSelector( `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] > [data-test-subj="indexPatternOption-${newIndexPatternTitle}"]` @@ -710,7 +721,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await deleteButton.isEnabled(); } - async canSavedObjectBeDeleted(id) { + async canSavedObjectBeDeleted(id: string) { const allCheckBoxes = await testSubjects.findAll('checkboxSelectRow*'); for (const checkBox of allCheckBoxes) { if (await checkBox.isSelected()) { @@ -722,6 +733,12 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await checkBox.click(); return await this.canSavedObjectsBeDeleted(); } + + async setNavType(navType: NavSetting) { + await PageObjects.common.navigateToApp('settings'); + await this.clickKibanaSettings(); + await this.setAdvancedSettingsSelect('pageNavigation', navType); + } } return new SettingsPage(); diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts index b6d13a5604011f..0cc64277efe11b 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_status.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -28,7 +28,7 @@ import '../../plugins/core_app_status/public/types'; // eslint-disable-next-line import/no-default-export export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'settings']); const browser = getService('browser'); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -48,6 +48,10 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }; describe('application status management', () => { + before(async () => { + await PageObjects.settings.setNavType('individual'); + }); + beforeEach(async () => { await PageObjects.common.navigateToApp('app_status_start'); }); diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index f50d4605325560..6567837f653095 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -122,7 +122,7 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }); it('can navigate from NP apps to legacy apps', async () => { - await appsMenu.clickLink('Management'); + await appsMenu.clickLink('Stack Management'); await loadingScreenShown(); await testSubjects.existOrFail('managementNav'); }); diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 0934cb0019f44c..c52e6742ddae58 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -9,6 +9,7 @@ import { Server } from 'hapi'; import { resolve } from 'path'; import { APMPluginContract } from '../../../plugins/apm/server'; import { LegacyPluginInitializer } from '../../../../src/legacy/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import mappings from './mappings.json'; import { makeApmUsageCollector } from './server/lib/apm_telemetry'; @@ -18,7 +19,6 @@ export const apm: LegacyPluginInitializer = kibana => { id: 'apm', configPrefix: 'xpack.apm', publicDir: resolve(__dirname, 'public'), - uiExports: { app: { title: 'APM', @@ -28,7 +28,8 @@ export const apm: LegacyPluginInitializer = kibana => { main: 'plugins/apm/index', icon: 'plugins/apm/icon.svg', euiIconType: 'apmApp', - order: 8100 + order: 8100, + category: DEFAULT_APP_CATEGORIES.observability }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), home: ['plugins/apm/legacy_register_feature'], diff --git a/x-pack/legacy/plugins/canvas/index.js b/x-pack/legacy/plugins/canvas/index.js index 8e742de6de9448..ebd4f35db8175c 100644 --- a/x-pack/legacy/plugins/canvas/index.js +++ b/x-pack/legacy/plugins/canvas/index.js @@ -5,6 +5,7 @@ */ import { resolve } from 'path'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { init } from './init'; import { mappings } from './server/mappings'; import { CANVAS_APP, CANVAS_TYPE, CUSTOM_ELEMENT_TYPE } from './common/lib'; @@ -23,6 +24,7 @@ export function canvas(kibana) { icon: 'plugins/canvas/icon.svg', euiIconType: 'canvasApp', main: 'plugins/canvas/legacy_start', + category: DEFAULT_APP_CATEGORIES.analyze, }, interpreter: [ 'plugins/canvas/browser_functions', diff --git a/x-pack/legacy/plugins/dashboard_mode/index.js b/x-pack/legacy/plugins/dashboard_mode/index.js index 4a042498443220..94655adf981b48 100644 --- a/x-pack/legacy/plugins/dashboard_mode/index.js +++ b/x-pack/legacy/plugins/dashboard_mode/index.js @@ -5,15 +5,13 @@ */ import { resolve } from 'path'; - +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from './common'; - import { createDashboardModeRequestInterceptor } from './server'; -import { i18n } from '@kbn/i18n'; - // Copied largely from plugins/kibana/index.js. The dashboard viewer includes just the dashboard section of -// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc) +// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc) // since it's view only, but we want the urls to be the same, so we are using largely the same setup. export function dashboardMode(kibana) { const kbnBaseUrl = '/app/kibana'; @@ -64,6 +62,7 @@ export function dashboardMode(kibana) { } ), icon: 'plugins/kibana/dashboard/assets/dashboard.svg', + category: DEFAULT_APP_CATEGORIES.analyze, }, ], }, diff --git a/x-pack/legacy/plugins/graph/index.ts b/x-pack/legacy/plugins/graph/index.ts index 601a239574e6b5..f798fa5e9f39d4 100644 --- a/x-pack/legacy/plugins/graph/index.ts +++ b/x-pack/legacy/plugins/graph/index.ts @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import migrations from './migrations'; import mappings from './mappings.json'; import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const graph: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -25,6 +26,7 @@ export const graph: LegacyPluginInitializer = kibana => { icon: 'plugins/graph/icon.png', euiIconType: 'graphApp', main: 'plugins/graph/index', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index f34b82d6bb1a3c..d1fcbea2ff5b72 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -146,7 +146,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { ); if (noIndexPatterns) { - const managementUrl = chrome.navLinks.get('kibana:management')!.url; + const managementUrl = chrome.navLinks.get('kibana:stack_management')!.url; const indexPatternUrl = `${managementUrl}/kibana/index_patterns`; const sampleDataUrl = `${application.getUrlForApp( 'kibana' diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index 196950b51be3ab..d9abadcb5125c5 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -18,6 +18,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../plugins/fea import { SpacesPluginSetup } from '../../../plugins/spaces/server'; import { VisTypeTimeseriesSetup } from '../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginContract } from '../../../plugins/apm/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const APP_ID = 'infra'; @@ -55,6 +56,7 @@ export function infra(kibana: any) { defaultMessage: 'Metrics', }), url: `/app/${APP_ID}#/infrastructure`, + category: DEFAULT_APP_CATEGORIES.observability, }, { description: i18n.translate('xpack.infra.linkLogsDescription', { @@ -68,6 +70,7 @@ export function infra(kibana: any) { defaultMessage: 'Logs', }), url: `/app/${APP_ID}#/logs`, + category: DEFAULT_APP_CATEGORIES.observability, }, ], mappings: savedObjectMappings, diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index d38a23560fa9f7..4f679905fc352a 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; +import mappings from './mappings.json'; import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; -import mappings from './mappings.json'; import { migrations } from './migrations'; import { initTelemetryCollection } from './server/maps_telemetry'; import { getAppTitle } from './common/i18n_getters'; -import _ from 'lodash'; import { MapPlugin } from './server/plugin'; import { APP_ID, APP_ICON, createMapPath, MAP_SAVED_OBJECT_TYPE } from './common/constants'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export function maps(kibana) { return new kibana.Plugin({ @@ -29,6 +30,7 @@ export function maps(kibana) { main: 'plugins/maps/legacy', icon: 'plugins/maps/icon.svg', euiIconType: APP_ICON, + category: DEFAULT_APP_CATEGORIES.analyze, }, injectDefaultVars(server) { const serverConfig = server.config(); diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts index c4289389b0d56d..fc1cec7c16208a 100755 --- a/x-pack/legacy/plugins/ml/index.ts +++ b/x-pack/legacy/plugins/ml/index.ts @@ -10,7 +10,7 @@ import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { plugin } from './server/new_platform'; import { CloudSetup } from '../../../plugins/cloud/server'; - +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { MlInitializerContext, MlCoreSetup, @@ -42,6 +42,7 @@ export const ml = (kibana: any) => { icon: 'plugins/ml/application/ml.svg', euiIconType: 'machineLearningApp', main: 'plugins/ml/legacy', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/application/index.scss'), hacks: ['plugins/ml/application/hacks/toggle_app_link_in_nav'], diff --git a/x-pack/legacy/plugins/monitoring/ui_exports.js b/x-pack/legacy/plugins/monitoring/ui_exports.js index ba659aa74f10ca..2b5ea21a2bb452 100644 --- a/x-pack/legacy/plugins/monitoring/ui_exports.js +++ b/x-pack/legacy/plugins/monitoring/ui_exports.js @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; /** * Configuration of dependency objects for the UI, which are needed for the @@ -26,6 +27,7 @@ export const getUiExports = () => ({ euiIconType: 'monitoringApp', linkToLastSubUrl: false, main: 'plugins/monitoring/monitoring', + category: DEFAULT_APP_CATEGORIES.management, }, injectDefaultVars(server) { const config = server.config(); diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index edbb62feb580f8..f6f2ead2d64fac 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -32,6 +32,7 @@ import { } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; import { initServerWithKibana } from './server/kibana.index'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const siem = (kibana: any) => { @@ -62,6 +63,7 @@ export const siem = (kibana: any) => { order: 9000, title: APP_NAME, url: `/app/${APP_ID}`, + category: DEFAULT_APP_CATEGORIES.security, }, ], uiSettingDefaults: { diff --git a/x-pack/legacy/plugins/uptime/index.ts b/x-pack/legacy/plugins/uptime/index.ts index e090a2c85e1366..cf7332f97d466b 100644 --- a/x-pack/legacy/plugins/uptime/index.ts +++ b/x-pack/legacy/plugins/uptime/index.ts @@ -9,6 +9,7 @@ import { resolve } from 'path'; import { PluginInitializerContext } from 'src/core/server'; import { PLUGIN } from './common/constants'; import { KibanaServer, plugin } from './server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const uptime = (kibana: any) => new kibana.Plugin({ @@ -30,6 +31,7 @@ export const uptime = (kibana: any) => main: 'plugins/uptime/app', order: 8900, url: '/app/uptime#/', + category: DEFAULT_APP_CATEGORIES.observability, }, home: ['plugins/uptime/register_feature'], }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 88fd8360ec728f..d828556df4fa45 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -450,7 +450,6 @@ "common.ui.flotCharts.thuLabel": "木", "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", - "common.ui.management.breadcrumb": "管理", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", @@ -1887,8 +1886,6 @@ "kbn.management.landing.header": "Kibana {version} 管理", "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", "kbn.management.landing.text": "すべてのツールの一覧は、左のメニューにあります。", - "kbn.management.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", - "kbn.management.managementLabel": "管理", "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "削除", "kbn.management.objects.confirmModalOptions.modalDescription": "削除されたオブジェクトは復元できません", "kbn.management.objects.confirmModalOptions.modalTitle": "保存された Kibana オブジェクトを削除しますか?", @@ -13219,4 +13216,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ad62993d50f062..ac4152328717a0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -450,7 +450,6 @@ "common.ui.flotCharts.thuLabel": "周四", "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", - "common.ui.management.breadcrumb": "管理", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", @@ -1887,8 +1886,6 @@ "kbn.management.landing.header": "Kibana {version} 管理", "kbn.management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", "kbn.management.landing.text": "在左侧菜单中可找到完整工具列表", - "kbn.management.managementDescription": "您用于管理 Elastic Stack 的中心控制台。", - "kbn.management.managementLabel": "管理", "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "删除", "kbn.management.objects.confirmModalOptions.modalDescription": "您无法恢复删除的对象", "kbn.management.objects.confirmModalOptions.modalTitle": "删除已保存 Kibana 对象?", @@ -13218,4 +13215,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index ad4f81777e7804..2649c5d26309db 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -54,7 +54,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { expectSpaceSelector: false, } ); - await kibanaServer.uiSettings.replace({}); + await kibanaServer.uiSettings.replace({ pageNavigation: 'individual' }); await PageObjects.settings.navigateTo(); }); @@ -68,7 +68,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Management']); + expect(navLinks).to.eql(['Stack Management']); }); it(`allows settings to be changed`, async () => { @@ -124,7 +124,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Management']); + expect(navLinks).to.eql(['Stack Management']); }); it(`does not allow settings to be changed`, async () => { @@ -175,7 +175,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Discover', 'Management']); + expect(navLinks).to.eql(['Discover', 'Stack Management']); }); it(`does not allow navigation to advanced settings; redirects to Kibana home`, async () => { diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index ee58be76928b3a..79bb10e0bded16 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -40,8 +40,9 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.contain('Management'); + expect(navLinks).to.contain('Stack Management'); }); it(`allows settings to be changed`, async () => { diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index e2d5efac4644cc..7c9c9f9c8c155a 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -60,7 +60,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['APM', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['APM', 'Stack Management']); }); it('can navigate to APM app', async () => { @@ -109,7 +109,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['APM', 'Management']); + expect(navLinks).to.eql(['APM', 'Stack Management']); }); it('can navigate to APM app', async () => { diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts index 1ac1784e0e05db..474240b201face 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security']); + const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security', 'settings']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -30,6 +30,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('APM'); }); diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index d0e37ec8e3f359..71c10bd8248be8 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -66,7 +66,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Canvas', 'Management']); + expect(navLinks).to.eql(['Canvas', 'Stack Management']); }); it(`landing page shows "Create new workpad" button`, async () => { @@ -142,7 +142,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Canvas', 'Management']); + expect(navLinks).to.eql(['Canvas', 'Stack Management']); }); it(`landing page shows disabled "Create new workpad" button`, async () => { diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts index 28b572401892b5..5395f125bbd22b 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'canvas', 'security', 'spaceSelector']); + const PageObjects = getPageObjects(['common', 'canvas', 'security', 'spaceSelector', 'settings']); const appsMenu = getService('appsMenu'); describe('spaces feature controls', function() { @@ -40,6 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Canvas'); }); diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index d25fae3c4894cd..6a6e2f23785e30 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -75,7 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['Dashboard', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['Dashboard', 'Stack Management']); }); it(`landing page shows "Create new Dashboard" button`, async () => { @@ -253,7 +253,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Dashboard', 'Management']); + expect(navLinks).to.eql(['Dashboard', 'Stack Management']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts index ebe08a60c25636..002ae627c488de 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts @@ -13,7 +13,13 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'dashboard', + 'security', + 'spaceSelector', + 'settings', + ]); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -43,6 +49,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Dashboard'); }); diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js index bab798dacc453b..1189fe909ca320 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js @@ -39,6 +39,7 @@ export default function({ getService, getPageObjects }) { await esArchiver.load('dashboard_view_mode'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', + pageNavigation: 'individual', }); await browser.setWindowSize(1600, 1000); @@ -199,7 +200,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.security.forceLogout(); await PageObjects.security.login('mixeduser', '123456'); - if (await appsMenu.linkExists('Management')) { + if (await appsMenu.linkExists('Stack Management')) { throw new Error('Expected management nav link to not be shown'); } }); @@ -208,7 +209,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.security.forceLogout(); await PageObjects.security.login('mysuperuser', '123456'); - if (!(await appsMenu.linkExists('Management'))) { + if (!(await appsMenu.linkExists('Stack Management'))) { throw new Error('Expected management nav link to be shown'); } }); diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts index 494fd71ea6f34a..9db9a913e9a4b5 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts @@ -63,7 +63,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Dev Tools navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['Dev Tools', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['Dev Tools', 'Stack Management']); }); describe('console', () => { @@ -144,7 +144,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it(`shows 'Dev Tools' navlink`, async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Dev Tools', 'Management']); + expect(navLinks).to.eql(['Dev Tools', 'Stack Management']); }); describe('console', () => { diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts index 4184d223a96864..f917792eea027f 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts @@ -9,7 +9,13 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'dashboard', + 'security', + 'spaceSelector', + 'settings', + ]); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); const grokDebugger = getService('grokDebugger'); @@ -40,6 +46,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Dev Tools'); }); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 1912b16d96f36c..1796858165a2ba 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -81,7 +81,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['Discover', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['Discover', 'Stack Management']); }); it('shows save button', async () => { @@ -168,7 +168,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Discover', 'Management']); + expect(navLinks).to.eql(['Discover', 'Stack Management']); }); it(`doesn't show save button`, async () => { diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts index e6b6f28f8b92fc..c38dda536f2530 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts @@ -15,6 +15,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { 'timePicker', 'security', 'spaceSelector', + 'settings', ]); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -49,6 +50,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Discover'); }); diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index a2b062e6ef84fb..37de93a0a7e910 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -64,7 +64,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['Graph', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['Graph', 'Stack Management']); }); it('landing page shows "Create new graph" button', async () => { @@ -127,7 +127,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Graph', 'Management']); + expect(navLinks).to.eql(['Graph', 'Stack Management']); }); it('does not show a "Create new Workspace" button', async () => { diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts index a0b0d5bef96680..d0d0232b5a8b14 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'graph', 'security', 'error']); + const PageObjects = getPageObjects(['common', 'graph', 'security', 'error', 'settings']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -34,6 +34,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Graph'); }); diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index 30cdc95b38e62e..ed25816e68712b 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -70,7 +70,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Management']); + expect(navLinks).to.eql(['Stack Management']); }); it(`index pattern listing shows create button`, async () => { @@ -113,7 +113,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { } ); - await kibanaServer.uiSettings.replace({}); + await kibanaServer.uiSettings.replace({ pageNavigation: 'individual' }); await PageObjects.settings.navigateTo(); }); @@ -124,7 +124,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Management']); + expect(navLinks).to.eql(['Stack Management']); }); it(`index pattern listing doesn't show create button`, async () => { @@ -176,7 +176,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Discover', 'Management']); + expect(navLinks).to.eql(['Discover', 'Stack Management']); }); it(`doesn't show Index Patterns in management side-nav`, async () => { diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts index 6a2b77de17f457..75020d6eab7e4a 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts @@ -40,8 +40,9 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.contain('Management'); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing shows create button`, async () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 5062f094061c03..b7c5667a575065 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -61,7 +61,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Metrics', 'Management']); + expect(navLinks).to.eql(['Metrics', 'Stack Management']); }); describe('infrastructure landing page without data', () => { @@ -174,7 +174,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Metrics', 'Management']); + expect(navLinks).to.eql(['Metrics', 'Stack Management']); }); describe('infrastructure landing page without data', () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 7c2a11a542d66e..90458ef53dfc28 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -12,7 +12,13 @@ const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'infraHome', + 'security', + 'spaceSelector', + 'settings', + ]); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const retry = getService('retry'); @@ -31,7 +37,6 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { // we need to load the following in every situation as deleting // a space deletes all of the associated saved objects await esArchiver.load('empty_kibana'); - await spacesService.create({ id: 'custom_space', name: 'custom_space', @@ -48,6 +53,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Metrics'); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index b9634c29dac1c9..5008f93feeb015 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -58,7 +58,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Logs', 'Management']); + expect(navLinks).to.eql(['Logs', 'Stack Management']); }); describe('logs landing page without data', () => { @@ -121,7 +121,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Logs', 'Management']); + expect(navLinks).to.eql(['Logs', 'Stack Management']); }); describe('logs landing page without data', () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts index 6b078d2cfa71af..61a57e09f96c57 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts @@ -9,7 +9,13 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'infraHome', + 'security', + 'spaceSelector', + 'settings', + ]); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -36,6 +42,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Logs'); }); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index 8fb6f21c778d3f..c25c1bfe4b7318 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -10,7 +10,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const security = getService('security'); const appsMenu = getService('appsMenu'); - const PageObjects = getPageObjects(['common', 'security']); + const PageObjects = getPageObjects(['common', 'security', 'settings']); describe('security', () => { before(async () => { @@ -94,6 +94,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); await PageObjects.security.login('machine_learning_user', 'machine_learning_user-password'); + await PageObjects.settings.setNavType('individual'); }); after(async () => { diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index fc94688e98811b..c633852a2da0a7 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error', 'settings']); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -39,6 +39,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Machine Learning'); }); diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts index 804ad5725edfd3..ece162cbd96cc7 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts @@ -66,7 +66,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Maps', 'Management']); + expect(navLinks).to.eql(['Maps', 'Stack Management']); }); it(`allows a map to be created`, async () => { @@ -153,7 +153,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Maps', 'Management']); + expect(navLinks).to.eql(['Maps', 'Stack Management']); }); it(`does not show create new button`, async () => { @@ -248,7 +248,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('does not show Maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Discover', 'Management']); + expect(navLinks).to.eql(['Discover', 'Stack Management']); }); it(`returns a 404`, async () => { diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index d985da42ab5eda..130aefb3cae2ac 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -10,7 +10,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const security = getService('security'); const appsMenu = getService('appsMenu'); - const PageObjects = getPageObjects(['common', 'security']); + const PageObjects = getPageObjects(['common', 'security', 'settings']); describe('security', () => { before(async () => { @@ -97,6 +97,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows monitoring navlink', async () => { + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Stack Monitoring'); }); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 7459b53ca4a32f..0465cbcf54541c 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error', 'settings']); const appsMenu = getService('appsMenu'); const find = getService('find'); @@ -37,10 +37,11 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await spacesService.delete('custom_space'); }); - it('shows Stack Monitoring navlink', async () => { + it('shows Stack Monitoring navlink fail', async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Stack Monitoring'); }); diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts index 1e79c76bf83e5e..d71d197a6ea199 100644 --- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts +++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts @@ -16,6 +16,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { describe('security feature controls', () => { before(async () => { await esArchiver.load('empty_kibana'); + await PageObjects.settings.setNavType('individual'); }); after(async () => { @@ -56,7 +57,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.contain('Management'); + expect(navLinks).to.contain('Stack Management'); }); it(`displays Spaces management section`, async () => { @@ -130,7 +131,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.contain('Management'); + expect(navLinks).to.contain('Stack Management'); }); it(`doesn't display Spaces management section`, async () => { diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts index dea45f161e4510..62483a10552e34 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts @@ -60,7 +60,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Timelion', 'Management']); + expect(navLinks).to.eql(['Timelion', 'Stack Management']); }); it(`allows a timelion sheet to be created`, async () => { @@ -112,7 +112,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Timelion', 'Management']); + expect(navLinks).to.eql(['Timelion', 'Stack Management']); }); it(`does not allow a timelion sheet to be created`, async () => { diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts index fb203a23359bdd..7e0fe731301a64 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts @@ -9,7 +9,13 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'timelion', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'timelion', + 'security', + 'spaceSelector', + 'settings', + ]); const appsMenu = getService('appsMenu'); describe('timelion', () => { @@ -38,6 +44,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Timelion'); }); diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts index a004f8db66823c..4ff82484db91c4 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts @@ -64,7 +64,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map(link => link.text)).to.eql(['Uptime', 'Management']); + expect(navLinks.map(link => link.text)).to.eql(['Uptime', 'Stack Management']); }); it('can navigate to Uptime app', async () => { @@ -115,7 +115,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Uptime', 'Management']); + expect(navLinks).to.eql(['Uptime', 'Stack Management']); }); it('can navigate to Uptime app', async () => { diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts index 77c5b323340bf8..c3dcb1b27771fb 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security']); + const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security', 'settings']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -30,6 +30,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Uptime'); }); diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index d55076cb0ab43b..767dbd71655672 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -75,7 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Visualize', 'Management']); + expect(navLinks).to.eql(['Visualize', 'Stack Management']); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -189,7 +189,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map(link => link.text); - expect(navLinks).to.eql(['Visualize', 'Management']); + expect(navLinks).to.eql(['Visualize', 'Stack Management']); }); it(`landing page shows "Create new Visualization" button`, async () => { diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts index 9193862d2ba9e4..066042896c122f 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts @@ -10,7 +10,13 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'visualize', 'security', 'spaceSelector']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'security', + 'spaceSelector', + 'settings', + ]); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -40,6 +46,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + await PageObjects.settings.setNavType('individual'); const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Visualize'); }); diff --git a/x-pack/test/ui_capabilities/common/nav_links_builder.ts b/x-pack/test/ui_capabilities/common/nav_links_builder.ts index 8b7741469362e8..aaeb22852bcc09 100644 --- a/x-pack/test/ui_capabilities/common/nav_links_builder.ts +++ b/x-pack/test/ui_capabilities/common/nav_links_builder.ts @@ -13,7 +13,7 @@ export class NavLinksBuilder { ...features, // management isn't a first-class "feature", but it makes our life easier here to pretend like it is management: { - navLinkId: 'kibana:management', + navLinkId: 'kibana:stack_management', }, }; } diff --git a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts index 4af7d81e5a7b4f..5c13e6b0eb51eb 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -68,7 +68,7 @@ export class UICapabilitiesService { : {}; const response = await this.axios.post( `${spaceUrlPrefix}/api/core/capabilities`, - { applications: [...applications, 'kibana:management'] }, + { applications: [...applications, 'kibana:stack_management'] }, { headers: requestHeaders, } From fc21c49c3565cadc77c2929c8012df66f244133d Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Tue, 21 Jan 2020 19:20:53 +0100 Subject: [PATCH 26/59] [SIEM] Fix Detections page breadcrumbs (#55173) --- package.json | 3 +- .../link_to/redirect_to_detection_engine.tsx | 18 +-- .../navigation/breadcrumbs/index.ts | 22 ++++ .../components/navigation/index.test.tsx | 1 + .../public/components/navigation/index.tsx | 116 ++++++++++-------- .../detection_engine/detection_engine.tsx | 9 +- .../public/pages/detection_engine/index.tsx | 11 +- .../detection_engine/rules/details/index.tsx | 10 +- .../detection_engine/rules/edit/index.tsx | 4 +- .../detection_engine/rules/translations.ts | 8 ++ .../pages/detection_engine/rules/utils.ts | 98 +++++++++++++++ .../siem/public/pages/hosts/details/utils.ts | 6 +- .../public/pages/network/ip_details/utils.ts | 6 +- .../siem/public/utils/route/helpers.ts | 1 + .../siem/public/utils/route/spy_routes.tsx | 20 ++- .../plugins/siem/public/utils/route/types.ts | 10 +- yarn.lock | 12 +- 17 files changed, 262 insertions(+), 93 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts diff --git a/package.json b/package.json index 8b96fcc9d396e7..9707d3863d295d 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,8 @@ "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", - "**/serialize-javascript": "^2.1.1" + "**/serialize-javascript": "^2.1.1", + "**/fast-deep-equal": "^3.1.1" }, "workspaces": { "packages": [ diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx index 0a91f38061734a..2b7a2f14dfea76 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx @@ -52,11 +52,15 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine ); }; -export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; + +export const getDetectionEngineUrl = () => `${baseDetectionEngineUrl}`; export const getDetectionEngineAlertUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`; -export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; -export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; -export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; -export const getEditRuleUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule`; + `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}`; +export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; +export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; +export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; +export const getRuleDetailsUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}`; +export const getEditRuleUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}/edit`; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts index 9eee5b21e83f3c..91055bca066c46 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts @@ -10,6 +10,7 @@ import { getOr, omit } from 'lodash/fp'; import { APP_NAME } from '../../../../common/constants'; import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/details/utils'; import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details'; +import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../pages/detection_engine/rules/utils'; import { SiemPageName } from '../../../pages/home/types'; import { RouteSpyState, HostRouteSpyState, NetworkRouteSpyState } from '../../../utils/route/types'; import { getOverviewUrl } from '../../link_to'; @@ -38,6 +39,9 @@ const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpySt const isHostsRoutes = (spyState: RouteSpyState): spyState is HostRouteSpyState => spyState != null && spyState.pageName === SiemPageName.hosts; +const isDetectionsRoutes = (spyState: RouteSpyState) => + spyState != null && spyState.pageName === SiemPageName.detections; + export const getBreadcrumbsForRoute = ( object: RouteSpyState & TabNavigationProps ): Breadcrumb[] | null => { @@ -76,6 +80,24 @@ export const getBreadcrumbsForRoute = ( ), ]; } + if (isDetectionsRoutes(spyState) && object.navTabs) { + const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false }; + let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; + if (spyState.tabName != null) { + urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; + } + + return [ + ...siemRootBreadcrumb, + ...getDetectionRulesBreadcrumbs( + spyState, + urlStateKeys.reduce( + (acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)], + [] + ) + ), + ]; + } if ( spyState != null && object.navTabs && diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index b6efc07ad8fe3b..56be39f67b1bdd 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -187,6 +187,7 @@ describe('SIEM Navigation', () => { query: { language: 'kuery', query: '' }, savedQuery: undefined, search: '', + state: undefined, tabName: 'authentications', timeline: { id: '', isOpen: false }, timerange: { diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index 61ac84667d80f7..040a6e7847b772 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; +import isEqual from 'lodash/fp/isEqual'; +import deepEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; @@ -16,67 +17,78 @@ import { setBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; -export const SiemNavigationComponent = React.memo< - SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState ->( - ({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState, flowTarget }) => { - useEffect(() => { - if (pathName) { - setBreadcrumbs({ - query: urlState.query, - detailName, - filters: urlState.filters, - navTabs, - pageName, - pathName, - savedQuery: urlState.savedQuery, - search, - tabName, - flowTarget, - timerange: urlState.timerange, - timeline: urlState.timeline, - }); - } - }, [pathName, search, navTabs, urlState]); +export const SiemNavigationComponent: React.FC = ({ + detailName, + display, + navTabs, + pageName, + pathName, + search, + tabName, + urlState, + flowTarget, + state, +}) => { + useEffect(() => { + if (pathName) { + setBreadcrumbs({ + query: urlState.query, + detailName, + filters: urlState.filters, + navTabs, + pageName, + pathName, + savedQuery: urlState.savedQuery, + search, + tabName, + flowTarget, + timerange: urlState.timerange, + timeline: urlState.timeline, + state, + }); + } + }, [pathName, search, navTabs, urlState, state]); - return ( - - ); - }, - (prevProps, nextProps) => { - return ( + return ( + + ); +}; + +export const SiemNavigationRedux = compose< + React.ComponentClass +>(connect(makeMapStateToProps))( + React.memo( + SiemNavigationComponent, + (prevProps, nextProps) => prevProps.pathName === nextProps.pathName && prevProps.search === nextProps.search && isEqual(prevProps.navTabs, nextProps.navTabs) && - isEqual(prevProps.urlState, nextProps.urlState) - ); - } + isEqual(prevProps.urlState, nextProps.urlState) && + deepEqual(prevProps.state, nextProps.state) + ) ); -SiemNavigationComponent.displayName = 'SiemNavigationComponent'; - -export const SiemNavigationRedux = compose< - React.ComponentClass ->(connect(makeMapStateToProps))(SiemNavigationComponent); - -export const SiemNavigation = React.memo(props => { +const SiemNavigationContainer: React.FC = props => { const [routeProps] = useRouteSpy(); const stateNavReduxProps: RouteSpyState & SiemNavigationProps = { ...routeProps, ...props, }; + return ; -}); +}; -SiemNavigation.displayName = 'SiemNavigation'; +export const SiemNavigation = SiemNavigationContainer; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index d9e0377b34060e..5586749ce38d83 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -230,8 +230,13 @@ const makeMapStateToProps = () => { }; }; -export const DetectionEngine = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(DetectionEngineComponent); +}; + +export const DetectionEngine = connect( + makeMapStateToProps, + mapDispatchToProps +)(DetectionEngineComponent); DetectionEngine.displayName = 'DetectionEngine'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 33186d2787d8ae..6db8d93e46ac9d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -19,7 +19,7 @@ const detectionEnginePath = `/:pageName(detections)`; type Props = Partial> & { url: string }; -export const DetectionEngineContainer = React.memo(() => ( +const DetectionEngineContainerComponent: React.FC = () => ( (() => ( - + - + (() => ( /> -)); -DetectionEngineContainer.displayName = 'DetectionEngineContainer'; +); + +export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index a23c681a5aab2f..40c694160f73bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -109,7 +109,7 @@ const RuleDetailsComponent = memo( hasIndexWrite, signalIndexName, } = useUserInfo(); - const { ruleId } = useParams(); + const { detailName: ruleId } = useParams(); const [isLoading, rule] = useRule(ruleId); // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); @@ -381,7 +381,7 @@ const RuleDetailsComponent = memo( }} - + ); } @@ -402,8 +402,10 @@ const makeMapStateToProps = () => { }; }; -export const RuleDetails = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(RuleDetailsComponent); +}; + +export const RuleDetails = connect(makeMapStateToProps, mapDispatchToProps)(RuleDetailsComponent); RuleDetails.displayName = 'RuleDetails'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 9b7833afd7f4da..be56e916ae6c94 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -57,7 +57,7 @@ export const EditRuleComponent = memo(() => { canUserCRUD, hasManageApiKey, } = useUserInfo(); - const { ruleId } = useParams(); + const { detailName: ruleId } = useParams(); const [loading, rule] = useRule(ruleId); const userHasNoPermissions = @@ -347,7 +347,7 @@ export const EditRuleComponent = memo(() => { - + ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index e1257007d44a3b..d144a6d56a1687 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -25,6 +25,14 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageT defaultMessage: 'Signal detection rules', }); +export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { + defaultMessage: 'Create', +}); + +export const EDIT_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.editPageTitle', { + defaultMessage: 'Edit', +}); + export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', { defaultMessage: 'Refresh', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts new file mode 100644 index 00000000000000..55772aa73ecf33 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Breadcrumb } from 'ui/chrome'; +import { isEmpty } from 'lodash/fp'; + +import { + getDetectionEngineUrl, + getDetectionEngineTabUrl, + getRulesUrl, + getRuleDetailsUrl, + getCreateRuleUrl, + getEditRuleUrl, +} from '../../../components/link_to/redirect_to_detection_engine'; +import * as i18nDetections from '../translations'; +import * as i18nRules from './translations'; +import { RouteSpyState } from '../../../utils/route/types'; + +const getTabBreadcrumb = (pathname: string, search: string[]) => { + const tabPath = pathname.split('/')[2]; + + if (tabPath === 'alerts') { + return { + text: i18nDetections.ALERT, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'signals') { + return { + text: i18nDetections.SIGNAL, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'rules') { + return { + text: i18nRules.PAGE_TITLE, + href: `${getRulesUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } +}; + +const isRuleCreatePage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/create'); + +const isRuleEditPage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/edit'); + +export const getBreadcrumbs = (params: RouteSpyState, search: string[]): Breadcrumb[] => { + let breadcrumb = [ + { + text: i18nDetections.PAGE_TITLE, + href: `${getDetectionEngineUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }, + ]; + + const tabBreadcrumb = getTabBreadcrumb(params.pathName, search); + + if (tabBreadcrumb) { + breadcrumb = [...breadcrumb, tabBreadcrumb]; + } + + if (params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.ruleName, + href: `${getRuleDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleCreatePage(params.pathName)) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.ADD_PAGE_TITLE, + href: `${getCreateRuleUrl()}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.EDIT_PAGE_TITLE, + href: `${getEditRuleUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + return breadcrumb; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts index 52e016502940b1..c321478f101741 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { hostsModel } from '../../../store'; import { HostsTableType } from '../../../store/hosts/model'; import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts'; @@ -29,7 +29,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getHostsUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getHostsUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; @@ -38,7 +38,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre ...breadcrumb, { text: params.detailName, - href: `${getHostDetailsUrl(params.detailName)}${search && search[1] ? search[1] : ''}`, + href: `${getHostDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, }, ]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts index fed832167a60e7..a468812e2718d3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { decodeIpv6 } from '../../../lib/helpers'; import { getNetworkUrl, getIPDetailsUrl } from '../../../components/link_to/redirect_to_network'; import { networkModel } from '../../../store/network'; @@ -28,7 +28,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getNetworkUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getNetworkUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; if (params.detailName != null) { @@ -37,7 +37,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): { text: decodeIpv6(params.detailName), href: `${getIPDetailsUrl(params.detailName, params.flowTarget)}${ - search && search[1] ? search[1] : '' + !isEmpty(search[1]) ? search[1] : '' }`, }, ]; diff --git a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts index 188ae9c6c1866d..39efccc9f45b85 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts @@ -15,6 +15,7 @@ export const initRouteSpy: RouteSpyState = { tabName: undefined, search: '', pathName: '/', + state: undefined, }; export const RouterSpyStateContext = createContext<[RouteSpyState, Dispatch]>([ diff --git a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx index 5c24b2f48488d8..c88562abef6ae0 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx @@ -8,6 +8,7 @@ import * as H from 'history'; import { isEqual } from 'lodash/fp'; import { memo, useEffect, useState } from 'react'; import { withRouter } from 'react-router-dom'; +import deepEqual from 'fast-deep-equal'; import { SpyRouteProps } from './types'; import { useRouteSpy } from './use_route_spy'; @@ -19,6 +20,7 @@ export const SpyRouteComponent = memo( match: { params: { pageName, detailName, tabName, flowTarget }, }, + state, }) => { const [isInitializing, setIsInitializing] = useState(true); const [route, dispatch] = useRouteSpy(); @@ -61,8 +63,24 @@ export const SpyRouteComponent = memo( }, }); } + } else { + if (pageName && !deepEqual(state, route.state)) { + dispatch({ + type: 'updateRoute', + route: { + pageName, + detailName, + tabName, + search, + pathName: pathname, + history, + flowTarget, + state, + }, + }); + } } - }, [pathname, search, pageName, detailName, tabName, flowTarget]); + }, [pathname, search, pageName, detailName, tabName, flowTarget, state]); return null; } ); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/types.ts b/x-pack/legacy/plugins/siem/public/utils/route/types.ts index 79d2677eff06f6..d3eca36bd0d96d 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/types.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/types.ts @@ -21,6 +21,7 @@ export interface RouteSpyState { pathName: string; history?: H.History; flowTarget?: FlowTarget; + state?: Record; } export interface HostRouteSpyState extends RouteSpyState { @@ -38,7 +39,10 @@ export type RouteSpyAction = } | { type: 'updateRouteWithOutSearch'; - route: Pick; + route: Pick< + RouteSpyState, + 'pageName' & 'detailName' & 'tabName' & 'pathName' & 'history' & 'state' + >; } | { type: 'updateRoute'; @@ -55,4 +59,6 @@ export type SpyRouteProps = RouteComponentProps<{ tabName: HostsTableType | undefined; search: string; flowTarget: FlowTarget | undefined; -}>; +}> & { + state?: Record; +}; diff --git a/yarn.lock b/yarn.lock index 198174d0132fae..3563ee3fc2733f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13308,17 +13308,7 @@ fancy-log@^1.3.2: color-support "^1.1.3" time-stamp "^1.0.0" -fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" - integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-deep-equal@^3.1.1: +fast-deep-equal@^1.0.0, fast-deep-equal@^2.0.1, fast-deep-equal@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== From 740d4d1afa7ea04f350855d2547d5e4f461e292a Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Jan 2020 13:23:49 -0500 Subject: [PATCH 27/59] [File upload] Enforce file-type check in file dialog (#55063) --- .../public/components/json_index_file_picker.js | 9 ++++++++- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js index f1e74919d734b3..0ee4f76ebf9d0c 100644 --- a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js +++ b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js @@ -13,6 +13,8 @@ import { MAX_FILE_SIZE } from '../../common/constants/file_import'; import _ from 'lodash'; const ACCEPTABLE_FILETYPES = ['json', 'geojson']; +const acceptedFileTypeString = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(','); +const acceptedFileTypeStringMessage = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(', '); export class JsonIndexFilePicker extends Component { state = { @@ -103,6 +105,7 @@ export class JsonIndexFilePicker extends Component { const splitNameArr = name.split('.'); const fileType = splitNameArr.pop(); if (!ACCEPTABLE_FILETYPES.includes(fileType)) { + //should only occur if browser does not accept the accept parameter throw new Error( i18n.translate('xpack.fileUpload.jsonIndexFilePicker.acceptableTypesError', { defaultMessage: 'File is not one of acceptable types: {types}', @@ -252,7 +255,10 @@ export class JsonIndexFilePicker extends Component { ) : ( {i18n.translate('xpack.fileUpload.jsonIndexFilePicker.formatsAccepted', { - defaultMessage: 'Formats accepted: .json, .geojson', + defaultMessage: 'Formats accepted: {acceptedFileTypeStringMessage}', + values: { + acceptedFileTypeStringMessage, + }, })}{' '}
} onChange={this._fileHandler} + accept={acceptedFileTypeString} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d828556df4fa45..f61dfa8d886c28 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5668,7 +5668,6 @@ "xpack.fileUpload.jsonIndexFilePicker.filePickerLabel": "アップロードするファイルを選択", "xpack.fileUpload.jsonIndexFilePicker.fileProcessingError": "ファイル処理エラー: {errorMessage}", "xpack.fileUpload.jsonIndexFilePicker.fileSizeError": "ファイルサイズエラー: {errorMessage}", - "xpack.fileUpload.jsonIndexFilePicker.formatsAccepted": "許可されている形式:.json、.geojson", "xpack.fileUpload.jsonIndexFilePicker.maxSize": "最大サイズ:{maxFileSize}", "xpack.fileUpload.jsonIndexFilePicker.noFileNameError": "ファイル名が指定されていません", "xpack.fileUpload.jsonIndexFilePicker.parsingFile": "{featuresProcessed} 件の機能が解析されました…", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ac4152328717a0..2c2e5325969838 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5667,7 +5667,6 @@ "xpack.fileUpload.jsonIndexFilePicker.filePickerLabel": "选择文件进行上传", "xpack.fileUpload.jsonIndexFilePicker.fileProcessingError": "文件处理错误:{errorMessage}", "xpack.fileUpload.jsonIndexFilePicker.fileSizeError": "文件大小错误:{errorMessage}", - "xpack.fileUpload.jsonIndexFilePicker.formatsAccepted": "接受的格式:.json、.geojson", "xpack.fileUpload.jsonIndexFilePicker.maxSize": "最大大小:{maxFileSize}", "xpack.fileUpload.jsonIndexFilePicker.noFileNameError": "未提供任何文件名称", "xpack.fileUpload.jsonIndexFilePicker.parsingFile": "{featuresProcessed} 特征已解析......", From 16b5fd7e04048d37d49081071a4c5c9bae2877a7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Jan 2020 13:41:36 -0500 Subject: [PATCH 28/59] [Uptime] Handle locations with names but no geo data (#55234) * Handle locations with names but no geo data. * Fix broken types, add a comment explaining some weird ts-related code. Co-authored-by: Elastic Machine --- .../location_map/embeddables/embedded_map.tsx | 6 ++--- .../location_map/embeddables/map_config.ts | 4 ++-- .../functional/location_map/location_map.tsx | 24 ++++++++++++------- .../elasticsearch_monitors_adapter.ts | 14 ++++++----- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 98780d23c5a629..11f6565734782e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -12,6 +12,7 @@ import { start } from '../../../../../../../../../src/legacy/core_plugins/embedd import * as i18n from './translations'; // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; +import { Location } from '../../../../../common/runtime_types'; import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; @@ -22,10 +23,7 @@ export interface EmbeddedMapProps { downPoints: LocationPoint[]; } -export interface LocationPoint { - lat: string; - lon: string; -} +export type LocationPoint = Required; const EmbeddedPanel = styled.div` z-index: auto; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts index d4601baefdf30c..a43edae4382527 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts @@ -5,7 +5,7 @@ */ import lowPolyLayerFeatures from './low_poly_layer.json'; -import { LocationPoint } from './embedded_map'; +import { LocationPoint } from './embedded_map.js'; import { UptimeAppColors } from '../../../../uptime_app'; /** @@ -16,7 +16,7 @@ import { UptimeAppColors } from '../../../../uptime_app'; export const getLayerList = ( upPoints: LocationPoint[], downPoints: LocationPoint[], - { gray, danger }: Pick + { danger }: Pick ) => { return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)]; }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index d35e1281260e2e..c93e16d0a080b5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -9,7 +9,7 @@ import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary } from '@elastic/eui'; import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; -import { MonitorLocations } from '../../../../common/runtime_types'; +import { MonitorLocations, MonitorLocation } from '../../../../common/runtime_types'; import { UNNAMED_LOCATION } from '../../../../common/constants'; import { LocationMissingWarning } from './location_missing'; @@ -32,15 +32,23 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { let isGeoInfoMissing = false; if (monitorLocations?.locations) { - monitorLocations.locations.forEach((item: any) => { - if (item.geo?.name !== UNNAMED_LOCATION) { - if (item.summary.down === 0) { - upPoints.push(item.geo.location); + monitorLocations.locations.forEach((item: MonitorLocation) => { + if (item.geo?.name === UNNAMED_LOCATION || !item.geo?.location) { + isGeoInfoMissing = true; + } else if ( + item.geo?.name !== UNNAMED_LOCATION && + !!item.geo.location.lat && + !!item.geo.location.lon + ) { + // TypeScript doesn't infer that the above checks in this block's condition + // ensure that lat and lon are defined when we try to pass the location object directly, + // but if we destructure the values it does. Improvement to this block is welcome. + const { lat, lon } = item.geo.location; + if (item?.summary?.down === 0) { + upPoints.push({ lat, lon }); } else { - downPoints.push(item.geo.location); + downPoints.push({ lat, lon }); } - } else if (item.geo?.name === UNNAMED_LOCATION) { - isGeoInfoMissing = true; } }); } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index c86e0db9ae04ad..e433931f03c8e6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -346,16 +346,18 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { const result = await callES('search', params); const locations = result?.aggregations?.location?.buckets ?? []; - const getGeo = (locGeo: any) => { + const getGeo = (locGeo: { name: string; location?: string }) => { if (locGeo) { const { name, location } = locGeo; - const latLon = location.trim().split(','); + const latLon = location?.trim().split(','); return { name, - location: { - lat: latLon[0], - lon: latLon[1], - }, + location: latLon + ? { + lat: latLon[0], + lon: latLon[1], + } + : undefined, }; } else { return { From 85edc661253617fbeafab207da3d5ed88273f04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 21 Jan 2020 19:42:22 +0100 Subject: [PATCH 29/59] =?UTF-8?q?[Logs=20UI]=20Use=20the=20correct=20icons?= =?UTF-8?q?=20and=20labels=20in=20the=20feature=20cont=E2=80=A6=20(#55292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes logs and metrics icons as well as the metrics label in the feature control lists which were missed during the renaming. fixes #55283 --- x-pack/legacy/plugins/infra/server/features.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/infra/server/features.ts b/x-pack/legacy/plugins/infra/server/features.ts index fc20813c777b69..02658002694d27 100644 --- a/x-pack/legacy/plugins/infra/server/features.ts +++ b/x-pack/legacy/plugins/infra/server/features.ts @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; export const METRICS_FEATURE = { id: 'infrastructure', name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', { - defaultMessage: 'Infrastructure', + defaultMessage: 'Metrics', }), - icon: 'infraApp', + icon: 'metricsApp', navLinkId: 'infra:home', app: ['infra', 'kibana'], catalogue: ['infraops'], @@ -40,7 +40,7 @@ export const LOGS_FEATURE = { name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', { defaultMessage: 'Logs', }), - icon: 'loggingApp', + icon: 'logsApp', navLinkId: 'infra:logs', app: ['infra', 'kibana'], catalogue: ['infralogging'], From 5a5bade8be51c2d93a3535dc9952e7ca47309dc0 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Tue, 21 Jan 2020 14:21:33 -0500 Subject: [PATCH 30/59] [Endpoint] Fix saga to start only after store is created and stopped on app unmount (#55245) - added `stop()`/`start()` methods to the Saga Middleware creator factory - adjust tests based on changes - changed application `renderApp` to stop sagas when react app is unmounted --- .../public/applications/endpoint/index.tsx | 3 +- .../applications/endpoint/lib/saga.test.ts | 57 ++++++++++++------- .../public/applications/endpoint/lib/saga.ts | 40 +++++++++++-- .../endpoint/store/endpoint_list/saga.test.ts | 15 +++-- .../applications/endpoint/store/index.ts | 11 ++-- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index d69e068bdea3ad..7598141bdea659 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -19,12 +19,13 @@ import { appStoreFactory } from './store'; export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMountParameters) { coreStart.http.get('/api/endpoint/hello-world'); - const store = appStoreFactory(coreStart); + const [store, stopSagas] = appStoreFactory(coreStart); ReactDOM.render(, element); return () => { ReactDOM.unmountComponentAtNode(element); + stopSagas(); }; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts index 0387eac0e7c7fa..91841f75c24fec 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts @@ -3,18 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { createSagaMiddleware, SagaContext } from './index'; -import { applyMiddleware, createStore, Reducer } from 'redux'; + +import { createSagaMiddleware, SagaContext, SagaMiddleware } from './index'; +import { applyMiddleware, createStore, Reducer, Store } from 'redux'; describe('saga', () => { const INCREMENT_COUNTER = 'INCREMENT'; const DELAYED_INCREMENT_COUNTER = 'DELAYED INCREMENT COUNTER'; const STOP_SAGA_PROCESSING = 'BREAK ASYNC ITERATOR'; - const sleep = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms)); + const sleep = (ms = 10) => new Promise(resolve => setTimeout(resolve, ms)); + let store: Store; let reducerA: Reducer; let sideAffect: (a: unknown, s: unknown) => void; let sagaExe: (sagaContext: SagaContext) => Promise; + let sagaExeReduxMiddleware: SagaMiddleware; beforeEach(() => { reducerA = jest.fn((prevState = { count: 0 }, { type }) => { @@ -47,53 +50,63 @@ describe('saga', () => { } } }); + + sagaExeReduxMiddleware = createSagaMiddleware(sagaExe); + store = createStore(reducerA, applyMiddleware(sagaExeReduxMiddleware)); }); - test('it returns Redux Middleware from createSagaMiddleware()', () => { - const sagaMiddleware = createSagaMiddleware(async () => {}); - expect(sagaMiddleware).toBeInstanceOf(Function); + afterEach(() => { + sagaExeReduxMiddleware.stop(); }); + test('it does nothing if saga is not started', () => { - const store = createStore(reducerA, applyMiddleware(createSagaMiddleware(sagaExe))); - expect(store.getState().count).toEqual(0); - expect(reducerA).toHaveBeenCalled(); - expect(sagaExe).toHaveBeenCalled(); - expect(sideAffect).not.toHaveBeenCalled(); - expect(store.getState()).toEqual({ count: 0 }); + expect(sagaExe).not.toHaveBeenCalled(); }); - test('it updates store once running', async () => { - const sagaMiddleware = createSagaMiddleware(sagaExe); - const store = createStore(reducerA, applyMiddleware(sagaMiddleware)); + test('it can dispatch store actions once running', async () => { + sagaExeReduxMiddleware.start(); expect(store.getState()).toEqual({ count: 0 }); expect(sagaExe).toHaveBeenCalled(); store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); expect(store.getState()).toEqual({ count: 0 }); - await sleep(100); + await sleep(); expect(sideAffect).toHaveBeenCalled(); expect(store.getState()).toEqual({ count: 1 }); }); - test('it stops processing if break out of loop', async () => { - const sagaMiddleware = createSagaMiddleware(sagaExe); - const store = createStore(reducerA, applyMiddleware(sagaMiddleware)); + test('it stops processing if break out of loop', async () => { + sagaExeReduxMiddleware.start(); store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(100); + await sleep(); expect(store.getState()).toEqual({ count: 1 }); expect(sideAffect).toHaveBeenCalledTimes(2); store.dispatch({ type: STOP_SAGA_PROCESSING }); - await sleep(100); + await sleep(); + + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); + await sleep(); + + expect(store.getState()).toEqual({ count: 1 }); + expect(sideAffect).toHaveBeenCalledTimes(2); + }); + + test('it stops saga middleware when stop() is called', async () => { + sagaExeReduxMiddleware.start(); + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); + await sleep(); expect(store.getState()).toEqual({ count: 1 }); expect(sideAffect).toHaveBeenCalledTimes(2); + sagaExeReduxMiddleware.stop(); + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(100); + await sleep(); expect(store.getState()).toEqual({ count: 1 }); expect(sideAffect).toHaveBeenCalledTimes(2); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts index b93360ec6b5aa7..bca6aa6563fe52 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts @@ -35,7 +35,20 @@ export interface SagaContext { dispatch: Dispatch; } +export interface SagaMiddleware extends Middleware { + /** + * Start the saga. Should be called after the `store` has been created + */ + start: () => void; + + /** + * Stop the saga by exiting the internal generator `for await...of` loop. + */ + stop: () => void; +} + const noop = () => {}; +const STOP = Symbol('STOP'); /** * Creates Saga Middleware for use with Redux. @@ -43,7 +56,7 @@ const noop = () => {}; * @param {Saga} saga The `saga` should initialize a long-running `for await...of` loop against * the return value of the `actionsAndState()` method provided by the `SagaContext`. * - * @return {Middleware} + * @return {SagaMiddleware} * * @example * @@ -64,22 +77,31 @@ const noop = () => {}; * //.... * const store = createStore(reducers, [ endpointsSagaMiddleware ]); */ -export function createSagaMiddleware(saga: Saga): Middleware { +export function createSagaMiddleware(saga: Saga): SagaMiddleware { const iteratorInstances = new Set(); let runSaga: () => void = noop; + let stopSaga: () => void = noop; + let runningPromise: Promise; async function* getActionsAndStateIterator(): StoreActionsAndState { const instance: IteratorInstance = { queue: [], nextResolve: null }; iteratorInstances.add(instance); + try { while (true) { - yield await nextActionAndState(); + const actionAndState = await Promise.race([nextActionAndState(), runningPromise]); + + if (actionAndState === STOP) { + break; + } + + yield actionAndState as QueuedAction; } } finally { // If the consumer stops consuming this (e.g. `break` or `return` is called in the `for await` // then this `finally` block will run and unregister this instance and reset `runSaga` iteratorInstances.delete(instance); - runSaga = noop; + runSaga = stopSaga = noop; } function nextActionAndState() { @@ -109,7 +131,6 @@ export function createSagaMiddleware(saga: Saga): Middleware { actionsAndState: getActionsAndStateIterator, dispatch, }); - runSaga(); } return (next: Dispatch) => (action: AnyAction) => { // Call the next dispatch method in the middleware chain. @@ -125,5 +146,14 @@ export function createSagaMiddleware(saga: Saga): Middleware { }; } + middleware.start = () => { + runningPromise = new Promise(resolve => (stopSaga = () => resolve(STOP))); + runSaga(); + }; + + middleware.stop = () => { + stopSaga(); + }; + return middleware; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts index 92bf3b7fd92dd8..6bf946873e1797 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts @@ -24,6 +24,7 @@ describe('endpoint list saga', () => { let fakeHttpServices: jest.Mocked; let store: Store; let dispatch: Dispatch; + let stopSagas: () => void; // TODO: consolidate the below ++ helpers in `index.test.ts` into a `test_helpers.ts`?? const generateEndpoint = (): EndpointData => { @@ -89,13 +90,19 @@ describe('endpoint list saga', () => { beforeEach(() => { fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); fakeHttpServices = fakeCoreStart.http as jest.Mocked; - store = createStore( - endpointListReducer, - applyMiddleware(createSagaMiddleware(endpointListSagaFactory())) - ); + + const sagaMiddleware = createSagaMiddleware(endpointListSagaFactory()); + store = createStore(endpointListReducer, applyMiddleware(sagaMiddleware)); + + sagaMiddleware.start(); + stopSagas = sagaMiddleware.stop; dispatch = store.dispatch; }); + afterEach(() => { + stopSagas(); + }); + test('it handles `userEnteredEndpointListPage`', async () => { const apiResponse = getEndpointListApiResponse(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts index d0dc002031ce2f..bfa1385b9f0ac0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createStore, compose, applyMiddleware } from 'redux'; +import { createStore, compose, applyMiddleware, Store } from 'redux'; import { CoreStart } from 'kibana/public'; import { appSagaFactory } from './saga'; import { appReducer } from './reducer'; @@ -15,10 +15,13 @@ const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMP ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'EndpointApp' }) : compose; -export const appStoreFactory = (coreStart: CoreStart) => { +export const appStoreFactory = (coreStart: CoreStart): [Store, () => void] => { + const sagaReduxMiddleware = appSagaFactory(coreStart); const store = createStore( appReducer, - composeWithReduxDevTools(applyMiddleware(appSagaFactory(coreStart))) + composeWithReduxDevTools(applyMiddleware(sagaReduxMiddleware)) ); - return store; + + sagaReduxMiddleware.start(); + return [store, sagaReduxMiddleware.stop]; }; From 0ac60494d676ddfd763024d57451618cf8792013 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 21 Jan 2020 14:01:28 -0600 Subject: [PATCH 31/59] [DOCS] Updates to heat map page (#55097) --- docs/user/visualize.asciidoc | 43 ++++++-- docs/visualize/aggregations.asciidoc | 144 +++++++++++--------------- docs/visualize/heatmap.asciidoc | 40 +++++++ docs/visualize/lens.asciidoc | 16 ++- docs/visualize/metric.asciidoc | 12 +++ docs/visualize/most-frequent.asciidoc | 37 ++++--- docs/visualize/regionmap.asciidoc | 15 +-- docs/visualize/tilemap.asciidoc | 31 +----- docs/visualize/tsvb.asciidoc | 24 +++-- 9 files changed, 211 insertions(+), 151 deletions(-) create mode 100644 docs/visualize/heatmap.asciidoc diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index e69d62daf74353..cfd2bac4989c11 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -3,9 +3,9 @@ [partintro] -- -_Visualize_ enables you to create visualizations of the data from your Elasticsearch indices, which you can then add to dashboards for analysis. +_Visualize_ enables you to create visualizations of the data from your {es} indices, which you can then add to dashboards for analysis. -{kib} visualizations are based on Elasticsearch queries. By using a series of {es} {ref}/search-aggregations.html[aggregations] to extract and process your data, you can create charts that show you the trends, spikes, and dips you need to know about. +{kib} visualizations are based on {es} queries. By using a series of {es} {ref}/search-aggregations.html[aggregations] to extract and process your data, you can create charts that show you the trends, spikes, and dips you need to know about. [float] [[create-a-visualization]] @@ -15,42 +15,59 @@ _Visualize_ enables you to create visualizations of the data from your Elasticse . Click *Create new visualization*. . Choose the visualization type: + -* *Basic charts* -<>:: Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. +* Basic charts +[horizontal] +<>:: +Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. + * *<>* [horizontal] Line, area, and bar charts:: Compare different series in X/Y charts. + Pie chart:: Display each source contribution to a total. + Data table:: Flattens aggregations into table format. + Metric:: Display a single number. + Goal and gauge:: Display a number with progress indicators. -Heat maps:: Display shaded cells within a matrix. + Tag cloud:: Display words in a cloud, where the size of the word corresponds to its importance. + * *Time series optimized* [horizontal] <>:: Visualize time series data using pipeline aggregations. + <>:: Compute and combine data from multiple time series data sets. + * *Maps* [horizontal] -<>:: The most powerful way of visualizing map data in {kib}. -<>:: Displays points on a map using a geohash aggregation. -<>:: Merge any structured map data onto a shape. -* *<>* +<>:: The most powerful way of visualizing map data in {kib}. + +<>:: Displays points on a map using a geohash aggregation. + +<>:: Merge any structured map data onto a shape. + +<>:: Display shaded cells within a matrix. + +* *<>* [horizontal] <>:: Provides the ability to add interactive inputs to a Dashboard. + <>:: Display free-form information or instructions. + * *For developers* [horizontal] <>:: Complete control over query and display. . Specify a search query to retrieve the data for your visualization: ** To enter new search criteria, select the <> for the indices that -contain the data you want to visualize. This opens the visualization builder +contain the data you want to visualize. The visualization builder opens with a wildcard query that matches all of the documents in the selected indices. ** To build a visualization from a saved search, click the name of the saved -search you want to use. This opens the visualization builder and loads the +search you want to use. The visualization builder opens and loads the selected query. + NOTE: When you build a visualization from a saved search, any subsequent @@ -58,9 +75,12 @@ modifications to the saved search are automatically reflected in the visualization. To disable automatic updates, you can disconnect a visualization from the saved search. + -- include::{kib-repo-dir}/visualize/visualize_rollup_data.asciidoc[] +include::{kib-repo-dir}/visualize/aggregations.asciidoc[] + include::{kib-repo-dir}/visualize/lens.asciidoc[] include::{kib-repo-dir}/visualize/most-frequent.asciidoc[] @@ -70,6 +90,7 @@ include::{kib-repo-dir}/visualize/timelion.asciidoc[] include::{kib-repo-dir}/visualize/tilemap.asciidoc[] include::{kib-repo-dir}/visualize/regionmap.asciidoc[] +include::{kib-repo-dir}/visualize/heatmap.asciidoc[] include::{kib-repo-dir}/visualize/for-dashboard.asciidoc[] diff --git a/docs/visualize/aggregations.asciidoc b/docs/visualize/aggregations.asciidoc index 36ddb0063dfc30..95aa586e6ba180 100644 --- a/docs/visualize/aggregations.asciidoc +++ b/docs/visualize/aggregations.asciidoc @@ -1,136 +1,112 @@ [[supported-aggregations]] -=== Supported aggregations +== Supported aggregations -The most frequently used visualizations support the following aggregations. +Use the supported aggregations to build your visualizations. [float] [[visualize-metric-aggregations]] -==== Metric aggregations +=== Metric aggregations -The *Count* metric lets you visualize the number of documents in a bucket. -If there are no bucket aggregations defined, this is the total number of documents that match the query. -It is the default selection. - -All other metric aggregations require a field selection, which will read from the indexed values. Alternatively, -you can override field values with a script using the <>. The -other metric aggregations are: +Metric aggregations extract field from documents to generate data values. {ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: The mean value. -{ref}/search-aggregations-metrics-max-aggregation.html[Maximum]:: The highest value. -{ref}/search-aggregations-metrics-percentile-aggregation.html[Median]:: The value that is in the 50% percentile. -{ref}/search-aggregations-metrics-min-aggregation.html[Minimum]:: The lowest value. -{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: The total value. -Unique Count:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[Cardinality] of the field within the bucket. -Supports any data type. +{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: The total number of documents that match the query, which allows you to visualize the number of documents in a bucket. Count is the default value. -Standard Deviation:: Requires a numeric field. Uses the {ref}/search-aggregations-metrics-extendedstats-aggregation.html[_extended stats_] aggregation. +{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: The highest value. + +{ref}/search-aggregations-metrics-percentile-aggregation.html[Median]:: The value that is in the 50% percentile. + +{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: The lowest value. -{ref}/search-aggregations-metrics-top-hits-aggregation.html[Top Hit]:: Returns a sample of individual documents. When the Top Hit aggregation is matched to more than one document, you must choose a technique for combining the values. Techniques include average, minimum, maximum, and sum. +{ref}/search-aggregations-metrics-percentile-rank-aggregation.html[Percentile ranks]:: Returns the percentile rankings for the values in the specified numeric field. Select a numeric field from the drop-down, then specify one or more percentile rank values in the *Values* fields. {ref}/search-aggregations-metrics-percentile-aggregation.html[Percentiles]:: Divides the -values in a numeric field into specified percentile bands. Select a field from the drop-down, then specify one or more ranges in the *Percentiles* fields. Click the *X* to remove a percentile field. Click *+ Add* to add a percentile field. +values in a numeric field into specified percentile bands. Select a field from the drop-down, then specify one or more ranges in the *Percentiles* fields. -{ref}/search-aggregations-metrics-percentile-rank-aggregation.html[Percentile Rank]:: Returns the percentile rankings for the values in the specified numeric field. Select a numeric field from the drop-down, then specify one or more percentile rank values in the *Values* fields. Click the *X* to remove a values field. Click *+Add* to add a values field. +Standard Deviation:: Requires a numeric field. Uses the {ref}/search-aggregations-metrics-extendedstats-aggregation.html[_extended stats_] aggregation. -[float] -[[visualize-sibling-pipeline-aggregations]] -==== Sibling pipeline aggregations +{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: The total value. -For each of the sibling pipeline aggregations you have to define a bucket and metric to calculate. This -has the effect of condensing many buckets into one number. +{ref}/search-aggregations-metrics-top-hits-aggregation.html[Top hit]:: Returns a sample of individual documents. When the Top Hit aggregation is matched to more than one document, you must choose a technique for combining the values. Techniques include average, minimum, maximum, and sum. -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Average Bucket]:: Calculates the mean, or average, value of a specified metric in a sibling aggregation. +Unique Count:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[Cardinality] of the field within the bucket. -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Sum Bucket]:: Calculates the sum of the values of a specified metric in a sibling aggregation. +Alternatively, you can override the field values with a script using JSON input. For example: -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Min Bucket]:: Calculates the minimum value of a specified metric in a sibling aggregation. +[source,shell] +{ "script" : "doc['grade'].value * 1.2" } -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Max Bucket]:: Calculates the maximum value of a specified metric in a sibling aggregation. +The example implements a {es} {ref}/search-aggregations.html[Script Value Source], which replaces +the value in the metric. The options available depend on the aggregation you choose. [float] -[[visualize-bucket-aggregations]] -==== Bucket aggregations - -{ref}/search-aggregations-bucket-datehistogram-aggregation.html[Date Histogram]:: Splits a date field into buckets by interval. If the date field is the primary time field for the index pattern, it will pick an automatic interval for you. You can also choose a minimum time interval, or specify a custom interval frame by selecting *Custom* as the interval and -specifying a number and a time unit in the text field. Custom interval time units are *s* for seconds, *m* for minutes, -*h* for hours, *d* for days, *w* for weeks, and *y* for years. Different units support different levels of precision, -down to one millisecond. Intervals are labeled at the start of the interval, using the date-key returned by Elasticsearch.For example, the tooltip for a monthly interval will show the first day of the month. +[[visualize-parent-pipeline-aggregations]] +=== Parent pipeline aggregations -{ref}/search-aggregations-bucket-histogram-aggregation.html[Histogram]:: Builds from a numeric field. Specify an integer interval for this field. Select the *Show empty buckets* checkbox to include empty intervals in the histogram. +Parent pipeline aggregations assume the bucket aggregations are ordered and are especially useful for time series data. For each parent pipeline aggregation, you must define a bucket aggregation and metric aggregation. -{ref}/search-aggregations-bucket-range-aggregation.html[Range]:: Specify ranges of values for a numeric field. Click *Add Range* to add a set of range endpoints. Click the red *(x)* symbol to remove a range. +You can also nest these aggregations. For example, if you want to produce a third derivative. -{ref}/search-aggregations-bucket-daterange-aggregation.html[Date Range]:: Reports values that are within a range of dates that you specify. You can specify the ranges for the dates using {ref}/common-options.html#date-math[_date math_] expressions. Click *Add Range* to add a set of range endpoints. -Click the red *(x)* symbol to remove a range. +{ref}/search-aggregations-pipeline-bucket-script-aggregation.html[Bucket script]:: Executes a script that performs computations for each bucket that specifies metrics in the parent multi-bucket aggregation. -{ref}/search-aggregations-bucket-iprange-aggregation.html[IPv4 Range]:: Specify ranges of IPv4 addresses. Click *Add Range* to add a set of range endpoints. Click the red *(x)* symbol to remove a range. +{ref}/search-aggregations-pipeline-cumulative-sum-aggregation.html[Cumulative sum]:: Calculates the cumulative sum of a specified metric in a parent histogram. -*Filters*:: Each filter creates a bucket of documents. You can specify a filter as a -<> or <> query string. Click *Add Filter* to -add another filter. Click the image:images/labelbutton.png[Label button icon] *label* button to open the label field, where -you can type in a name to display on the visualization. +{ref}/search-aggregations-pipeline-derivative-aggregation.html[Derivative]:: Calculates the derivative of specific metrics. -{ref}/search-aggregations-bucket-terms-aggregation.html[Terms]:: Specify the top or bottom _n_ elements of a given field to display, ordered by count or a custom metric. +{ref}/search-aggregations-pipeline-movavg-aggregation.html[Moving avg]:: Slides a window across the data and emits the average value of the window. -{ref}/search-aggregations-bucket-significantterms-aggregation.html[Significant Terms]:: Returns interesting or unusual occurrences of terms in a set. +{ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial diff]:: Values in a time series are subtracted from itself at different time lags or periods. -Both Terms and Significant Terms support {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns] which -are available by clicking *Advanced* after selecting a field. +Custom {kib} plugins can <>, which includes support for adding more aggregations. -Kibana only supports filtering string fields with regular expression patterns, it does not support matching with arrays or filtering numeric fields. -Patterns are case sensitive. +[float] +[[visualize-sibling-pipeline-aggregations]] +=== Sibling pipeline aggregations -Example: +Sibling pipeline aggregations condense many buckets into one. For each sibling pipeline aggregation, you must define a bucket aggregations and metric aggregation. -* You want to exclude the metricbeat process from your visualization of top processes: `metricbeat.*` -* You only want to show processes collecting beats: `.*beat` -* You want to exclude two specific values, the string `"empty"` and `"none"`: `empty|none` +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Average bucket]:: Calculates the mean, or average, value of a specified metric in a sibling aggregation. -*Geo aggregations* +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Max Bucket]:: Calculates the maximum value of a specified metric in a sibling aggregation. -These are only supported by the tile map and table visualizations: +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Min Bucket]:: Calculates the minimum value of a specified metric in a sibling aggregation. -{ref}/search-aggregations-bucket-geohashgrid-aggregation.html[Geohash]:: Displays points based on a geohash. +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Sum Bucket]:: Calculates the sum of the values of a specified metric in a sibling aggregation. -{ref}/search-aggregations-bucket-geotilegrid-aggregation.html[Geotile]:: Groups points based on web map tiling. +[float] +[[visualize-bucket-aggregations]] +=== Bucket aggregations +Bucket aggregations sort documents into buckets, depending on the contents of the document. -[float] -[[visualize-parent-pipeline-aggregations]] -==== Parent pipeline aggregations +{ref}/search-aggregations-bucket-datehistogram-aggregation.html[Date histogram]:: Splits a date field into buckets by interval. If the date field is the primary time field for the index pattern, it chooses an automatic interval for you. Intervals are labeled at the start of the interval, using the date-key returned by {es}. For example, the tooltip for a monthly interval displays the first day of the month. -For each of the parent pipeline aggregations you have to define a bucket and metric to calculate. These -metrics expect the buckets to be ordered, and are especially useful for time series data. -You can also nest these aggregations. For example, if you want to produce a third derivative. +{ref}/search-aggregations-bucket-daterange-aggregation.html[Date range]:: Reports values that are within a range of dates that you specify. You can specify the ranges for the dates using {ref}/common-options.html#date-math[_date math_] expressions. -These visualizations support parent pipeline aggregations: +{ref}/search-aggregations-bucket-filter-aggregation.html[Filter]:: Each filter creates a bucket of documents. You can specify a filter as a +<> or <> query string. -* Line, Area and Bar charts -* Data table +{ref}/search-aggregations-bucket-geohashgrid-aggregation.html[Geohash]:: Displays points based on a geohash. Supported by the tile map and data table visualizations. -{ref}/search-aggregations-pipeline-derivative-aggregation.html[Derivative]:: Calculates the derivative of specific metrics. +{ref}/search-aggregations-bucket-geotilegrid-aggregation.html[Geotile]:: Groups points based on web map tiling. Supported by the tile map and data table visualizations. -{ref}/search-aggregations-pipeline-cumulative-sum-aggregation.html[Cumulative Sum]:: Calculates the cumulative sum of a specified metric in a parent histogram. +{ref}/search-aggregations-bucket-histogram-aggregation.html[Histogram]:: Builds from a numeric field. -{ref}/search-aggregations-pipeline-movavg-aggregation.html[Moving Average]:: Slides a window across the data and emits the average value of the window. +{ref}/search-aggregations-bucket-iprange-aggregation.html[IPv4 range]:: Specify ranges of IPv4 addresses. -{ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial Diff]:: Values in a time series are subtracted from itself at different time lags or periods. +{ref}/search-aggregations-bucket-range-aggregation.html[Range]:: Specify ranges of values for a numeric field. -Custom {kib} plugins can <>, which includes support for adding more aggregations. +{ref}/search-aggregations-bucket-significantterms-aggregation.html[Significant terms]:: Returns interesting or unusual occurrences of terms in a set. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. -[float] -[[visualize-advanced-aggregation-options]] -==== Advanced aggregation options +{ref}/search-aggregations-bucket-terms-aggregation.html[Terms]:: Specify the top or bottom _n_ elements of a given field to display, ordered by count or a custom metric. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. -*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation -definition, as in the following example: +{kib} filters string fields with only regular expression patterns, and does not filter numeric fields or match with arrays. -[source,shell] -{ "script" : "doc['grade'].value * 1.2" } +For example: -This example implements a {es} {ref}/search-aggregations.html[Script Value Source] which replaces -the value in the metric. The availability of these options varies depending on the aggregation -you choose. +* You want to exclude the metricbeat process from your visualization of top processes: `metricbeat.*` +* You only want to show processes collecting beats: `.*beat` +* You want to exclude two specific values, the string `"empty"` and `"none"`: `empty|none` -When multiple bucket aggregations are defined, you can use the drag target on each aggregation to change the priority. For more information about working with aggregation order, see https://www.elastic.co/blog/kibana-aggregation-execution-order-and-you[Kibana, Aggregation Execution Order, and You]. +Patterns are case sensitive. diff --git a/docs/visualize/heatmap.asciidoc b/docs/visualize/heatmap.asciidoc new file mode 100644 index 00000000000000..18c4018213390b --- /dev/null +++ b/docs/visualize/heatmap.asciidoc @@ -0,0 +1,40 @@ +[[heatmap]] +== Heat map + +Heat maps are graphical representations of data where the individual values are represented as colors. + +NOTE: Heat map has been replaced with <>, which offers more functionality and is easier to use. + +[float] +[[heatmap-aggregation]] +=== Supported aggregations + +Heat maps support the following aggregations: + +* <> + +* <> + +* <> + +* <> + +[float] +[[navigate-heatmap]] +=== Change the color ranges + +When only one color displays on the heat map, you might need to change the color ranges. + +To specify the number of color ranges: + +. Click *Options*. + +. Enter the *Number of colors* to display. + +To specify custom ranges: + +. Click *Options*. + +. Select *Use custom ranges*. + +. Enter the ranges to display. diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index e61895a29891ba..e3f61565453b5e 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -4,7 +4,7 @@ beta[] -*Lens* provides you with a simple and fast way to create visualizations from your Elasticsearch data. With Lens, you can: +*Lens* provides you with a simple and fast way to create visualizations from your {es} data. With Lens, you can: * Quickly build visualizations by dragging and dropping data fields. @@ -14,6 +14,20 @@ beta[] * Save your visualization for use in a dashboard. +[float] +[[lens-aggregation]] +=== Supported aggregations + +Lens supports the following aggregations: + +* <> + +* <> + +* <> + +* <> + [float] [[drag-drop]] === Drag and drop diff --git a/docs/visualize/metric.asciidoc b/docs/visualize/metric.asciidoc index 9cbc4a0f7a5508..ddcf5fe3b73bdb 100644 --- a/docs/visualize/metric.asciidoc +++ b/docs/visualize/metric.asciidoc @@ -2,3 +2,15 @@ === Metric Click the *Options* tab to display the font size slider. + +[float] +[[metric-aggregation]] +==== Supported aggregations + +Metric support the following aggregations: + +* <> + +* <> + +* <> diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc index e9085d18185ec3..2cb8aa7cb3c1fb 100644 --- a/docs/visualize/most-frequent.asciidoc +++ b/docs/visualize/most-frequent.asciidoc @@ -1,30 +1,41 @@ [[most-frequent]] == Most frequently used visualizations -The most frequently used visualizations allow you to plot aggregated data from a <> or <>. They all support a single level of -Elasticsearch {es} {ref}/search-aggregations-metrics.html[metric] aggregations, and one or more -levels of {es} {ref}/search-aggregations-bucket.html[bucket] aggregations. +The most frequently used visualizations allow you to plot aggregated data from a <> or <>. The most frequently used visualizations include: * Line, area, and bar charts -* Pie charts -* Data tables -* Metric, goals, and gauges -* Heat maps -* Tag clouds +* Pie chart +* Data table +* Metric, goal, and gauge +* Tag cloud + +[float] +[[frequently-used-viz-aggregation]] +=== Supported aggregations + +The most frequently used visualizations support the following aggregations: + +* <> + +* <> + +* <> + +* <> [float] === Configure your visualization -You configure visualizations using the default editor, which is broken into metrics and buckets, and includes a default count -metric. Each visualization supports different configurations for what the metrics and buckets -represent. For example, a bar chart allows you to add an X-axis: +You configure visualizations using the default editor. Each visualization supports different configurations of the metrics and buckets. + +For example, a bar chart allows you to add an x-axis: [role="screenshot"] image::images/add-bucket.png["",height=478] -A common configuration for the X-axis is to use a {es} {ref}/search-aggregations-bucket-datehistogram-aggregation.html[date histogram] aggregation: +A common configuration for the x-axis is to use a {es} {ref}/search-aggregations-bucket-datehistogram-aggregation.html[date histogram] aggregation: [role="screenshot"] image::images/visualize-date-histogram.png[] @@ -58,5 +69,3 @@ Each visualization also has its own customization options. Most visualizations a [role="screenshot"] image::images/color-picker.png[An array of color dots that users can select,height=267] - -include::aggregations.asciidoc[] diff --git a/docs/visualize/regionmap.asciidoc b/docs/visualize/regionmap.asciidoc index c39282963ef7bc..accabd16e5fcdf 100644 --- a/docs/visualize/regionmap.asciidoc +++ b/docs/visualize/regionmap.asciidoc @@ -18,17 +18,18 @@ To create a region map, you configure an inner join that joins the result of an and a reference vector file based on a shared key. [float] -==== Data +[[region-map-aggregation]] +=== Supported aggregations -[float] -===== Metrics +Region maps support the following aggregations: -Select any of the supported _Metric_ or _Sibling Pipeline Aggregations_. +* <> -[float] -===== Buckets +* <> + +* <> -Configure a _Terms_ aggregation. The term is the _key_ that is used to join the results to the vector data on the map. +Use the _key_ term to join the results to the vector data on the map. [float] ==== Options diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc index 2b499d098a956e..08cf666345e34d 100644 --- a/docs/visualize/tilemap.asciidoc +++ b/docs/visualize/tilemap.asciidoc @@ -17,36 +17,11 @@ in `kibana.yml`. [[coordinate-map-aggregation]] === Supported aggregations -Coordinate maps support the metric and bucket aggregations. +Coordinate maps support the following aggregations: -[float] -===== Metric aggregations - -The following metric aggregations are supported: - -{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: Returns a raw count of -the elements in the index pattern. The default metrics aggregation for a coordinate map is *Count*. - -{ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: Returns the average of a numeric -field. - -{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: Returns the total sum of a numeric -field. - -{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: Returns the minimum value of a -numeric field. - -{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: Returns the maximum value of a -numeric field. - -{ref}/search-aggregations-metrics-cardinality-aggregation.html[Unique Count]:: Returns -the number of unique values in a field. - -[float] -[[coordinate-bucket-aggregation]] -===== Bucket aggregation +* <> -Coordinate maps support the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] bucket aggregation. +* <> When you deselect *Change precision on map zoom*, the *Precision* slider appears. The *Precision* slider determines the granularity of the results displayed on the map. For details on the area specified by each precision level, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid]. diff --git a/docs/visualize/tsvb.asciidoc b/docs/visualize/tsvb.asciidoc index ff4160d1ac9d20..69d6985acd1e42 100644 --- a/docs/visualize/tsvb.asciidoc +++ b/docs/visualize/tsvb.asciidoc @@ -1,8 +1,8 @@ [[TSVB]] == TSVB -TSVB is a time series data visualizer that allows you to use the full power of the -Elasticsearch aggregation framework. With TSVB, you can combine an infinite +TSVB is a time series data visualizer that allows you to use the full power of the +Elasticsearch aggregation framework. With TSVB, you can combine an infinite number of aggregations to display complex data. NOTE: In Elasticsearch version 7.3.0 and later, the time series data visualizer is now referred to as TSVB instead of Time Series Visual Builder. @@ -43,6 +43,18 @@ Table:: Display data from multiple time series by defining the field group to sh [role="screenshot"] image:images/tsvb-table.png["Table visualization"] +[float] +[[tsvb-aggregation]] +=== Supported aggregations + +TSVB supports the following aggregations: + +* <> + +* <> + +* <> + [float] [[create-tsvb-visualization]] === Create TSVB visualizations @@ -60,7 +72,7 @@ To create a single metric, add multiple data series with multiple aggregations. . Specify the data series labels and colors. .. Select *Data*. -+ ++ If you are using the *Table* visualization, select *Columns*. .. In the *Label* field, enter a name for the data series, which is used on legends and titles. @@ -79,7 +91,7 @@ For series that are grouped by a term, you can specify a mustache variable of `{ .. To add another metric, click *+*. + -When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. +When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. . To specify the format and display options, select *Options*. @@ -95,7 +107,7 @@ Change the data that you want to display and choose the style options for the pa . Select *Panel options*. -. Under *Data*, specify how much of the data that you want to display in the visualization. +. Under *Data*, specify how much of the data that you want to display in the visualization. . Under *Style*, specify how you want the visualization to look. @@ -113,7 +125,7 @@ If you are using the Time Series visualization, add annotation data sources. [[tsvb-enter-markdown]] ==== Enter Markdown text -Edit the source for the Markdown visualization. +Edit the source for the Markdown visualization. . Select *Markdown*. From 2aa87738a71f00c9406726fa63eed467b4c44d32 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Tue, 21 Jan 2020 14:48:16 -0600 Subject: [PATCH 32/59] [skip-ci] Update migration guide to add rendering service example (#54744) * Update migration guide to add rendering service example * Address review nits * Address nits * Add chromeless apps to TOC --- src/core/MIGRATION.md | 3 +- src/core/MIGRATION_EXAMPLES.md | 85 +++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index f51afd35586bd7..087888922ac9b9 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1193,10 +1193,11 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | | `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | +| `server.renderApp()` / `server.renderAppWithDefaultConfig()` | [`context.rendering.render()`](/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md) | [Examples](./MIGRATION_EXAMPLES.md#render-html-content) | | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | -| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | +| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | | `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | | | `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index d7964a53358efc..ab3cfc8941c5d9 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -16,7 +16,9 @@ APIs to their New Platform equivalents. - [Accessing Services](#accessing-services) - [Chrome](#chrome) - [Updating an application navlink](#updating-application-navlink) - + - [Chromeless Applications](#chromeless-applications) + - [Render HTML Content](#render-html-content) + ## Configuration ### Declaring config schema @@ -518,4 +520,83 @@ export class MyPlugin implements Plugin { }, }); } -``` \ No newline at end of file +``` + +## Chromeless Applications + +In Kibana, a "chromeless" application is one where the primary Kibana UI components +such as header or navigation can be hidden. In the legacy platform these were referred to +as "hidden" applications, and were set via the `hidden` property in a Kibana plugin. +Chromeless applications are also not displayed in the left navbar. + +To mark an application as chromeless, specify `chromeless: false` when registering your application +to hide the chrome UI when the application is mounted: + +```ts +application.register({ + id: 'chromeless', + chromeless: true, + async mount(context, params) { + /* ... */ + }, +}); +``` + +If you wish to render your application at a route that does not follow the `/app/${appId}` pattern, +this can be done via the `appRoute` property. Doing this currently requires you to register a server +route where you can return a bootstrapped HTML page for your application bundle. Instructions on +registering this server route is covered in the next section: [Render HTML Content](#render-html-content). + +```ts +application.register({ + id: 'chromeless', + appRoute: '/chromeless', + chromeless: true, + async mount(context, params) { + /* ... */ + }, +}); +``` + +## Render HTML Content + +You can return a blank HTML page bootstrapped with the core application bundle from an HTTP route handler +via the `rendering` context. You may wish to do this if you are rendering a chromeless application with a +custom application route or have other custom rendering needs. + +```ts +router.get( + { path: '/chromeless', validate: false }, + (context, request, response) => { + const { http, rendering } = context.core; + + return response.ok({ + body: await rendering.render(), // generates an HTML document + headers: { + 'content-security-policy': http.csp.header, + }, + }); + } +); +``` + +You can also specify to exclude user data from the bundle metadata. User data +comprises all UI Settings that are *user provided*, then injected into the page. +You may wish to exclude fetching this data if not authorized or to slim the page +size. + +```ts +router.get( + { path: '/', validate: false }, + (context, request, response) => { + const { http, rendering } = context.core; + + return response.ok({ + body: await rendering.render({ includeUserSettings: false }), + headers: { + 'content-security-policy': http.csp.header, + }, + }); + } +); +``` From 0cd1733c8ae6b8a8a60744cc33ccdd9a1ad54e70 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 21 Jan 2020 15:54:05 -0500 Subject: [PATCH 33/59] Skip failing endpoint saga tests --- .../endpoint/public/applications/endpoint/lib/saga.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts index 91841f75c24fec..971a50e6f900d9 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts @@ -7,7 +7,8 @@ import { createSagaMiddleware, SagaContext, SagaMiddleware } from './index'; import { applyMiddleware, createStore, Reducer, Store } from 'redux'; -describe('saga', () => { +// Failing: https://github.com/elastic/kibana/issues/55464 https://github.com/elastic/kibana/issues/55465 +describe.skip('saga', () => { const INCREMENT_COUNTER = 'INCREMENT'; const DELAYED_INCREMENT_COUNTER = 'DELAYED INCREMENT COUNTER'; const STOP_SAGA_PROCESSING = 'BREAK ASYNC ITERATOR'; From 7eb934e80c29447591e9a7735f85fbaba9b9c521 Mon Sep 17 00:00:00 2001 From: Pedro Jaramillo Date: Tue, 21 Jan 2020 16:00:52 -0500 Subject: [PATCH 34/59] Resolver zoom, pan, and center controls (#55221) * Resolver zoom, pan, and center controls * add tests, fix north panning * fix type issue * update west and east panning to behave like google maps --- .../resolver/store/camera/action.ts | 23 ++- .../resolver/store/camera/panning.test.ts | 62 ++++++++ .../resolver/store/camera/reducer.ts | 38 ++++- .../resolver/store/camera/selectors.ts | 7 + .../resolver/store/camera/zooming.test.ts | 27 +++- .../embeddables/resolver/store/selectors.ts | 5 + .../public/embeddables/resolver/types.ts | 5 + .../resolver/view/graph_controls.tsx | 148 ++++++++++++++++++ .../embeddables/resolver/view/index.tsx | 28 ++-- 9 files changed, 327 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/embeddables/resolver/view/graph_controls.tsx diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts index 090d5de901318e..4153070ab04e7e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Vector2 } from '../../types'; +import { Vector2, PanDirection } from '../../types'; interface UserSetZoomLevel { readonly type: 'userSetZoomLevel'; @@ -14,6 +14,14 @@ interface UserSetZoomLevel { readonly payload: number; } +interface UserClickedZoomOut { + readonly type: 'userClickedZoomOut'; +} + +interface UserClickedZoomIn { + readonly type: 'userClickedZoomIn'; +} + interface UserZoomed { readonly type: 'userZoomed'; /** @@ -56,6 +64,14 @@ interface UserStoppedPanning { readonly type: 'userStoppedPanning'; } +interface UserClickedPanControl { + readonly type: 'userClickedPanControl'; + /** + * String that represents the direction in which Resolver can be panned + */ + readonly payload: PanDirection; +} + interface UserMovedPointer { readonly type: 'userMovedPointer'; /** @@ -72,4 +88,7 @@ export type CameraAction = | UserStartedPanning | UserStoppedPanning | UserZoomed - | UserMovedPointer; + | UserMovedPointer + | UserClickedZoomOut + | UserClickedZoomIn + | UserClickedPanControl; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/panning.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/panning.test.ts index c09320182e3be0..17401a63b5ae8f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/panning.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/panning.test.ts @@ -58,4 +58,66 @@ describe('panning interaction', () => { }); }); }); + describe('panning controls', () => { + describe('when user clicks on pan north button', () => { + beforeEach(() => { + const action: CameraAction = { type: 'userClickedPanControl', payload: 'north' }; + store.dispatch(action); + }); + it('moves the camera south so that objects appear closer to the bottom of the screen', () => { + const actual = translation(store.getState()); + expect(actual).toMatchInlineSnapshot(` + Array [ + 0, + -32.49906769231164, + ] + `); + }); + }); + describe('when user clicks on pan south button', () => { + beforeEach(() => { + const action: CameraAction = { type: 'userClickedPanControl', payload: 'south' }; + store.dispatch(action); + }); + it('moves the camera north so that objects appear closer to the top of the screen', () => { + const actual = translation(store.getState()); + expect(actual).toMatchInlineSnapshot(` + Array [ + 0, + 32.49906769231164, + ] + `); + }); + }); + describe('when user clicks on pan east button', () => { + beforeEach(() => { + const action: CameraAction = { type: 'userClickedPanControl', payload: 'east' }; + store.dispatch(action); + }); + it('moves the camera west so that objects appear closer to the left of the screen', () => { + const actual = translation(store.getState()); + expect(actual).toMatchInlineSnapshot(` + Array [ + -32.49906769231164, + 0, + ] + `); + }); + }); + describe('when user clicks on pan west button', () => { + beforeEach(() => { + const action: CameraAction = { type: 'userClickedPanControl', payload: 'west' }; + store.dispatch(action); + }); + it('moves the camera east so that objects appear closer to the right of the screen', () => { + const actual = translation(store.getState()); + expect(actual).toMatchInlineSnapshot(` + Array [ + 32.49906769231164, + 0, + ] + `); + }); + }); + }); }); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts index b7229240684f15..7c4678a4f1dc13 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts @@ -9,7 +9,7 @@ import { applyMatrix3, subtract } from '../../lib/vector2'; import { userIsPanning, translation, projectionMatrix, inverseProjectionMatrix } from './selectors'; import { clamp } from '../../lib/math'; -import { CameraState, ResolverAction } from '../../types'; +import { CameraState, ResolverAction, Vector2 } from '../../types'; import { scaleToZoom } from './scale_to_zoom'; function initialState(): CameraState { @@ -34,6 +34,16 @@ export const cameraReducer: Reducer = ( ...state, scalingFactor: clamp(action.payload, 0, 1), }; + } else if (action.type === 'userClickedZoomIn') { + return { + ...state, + scalingFactor: clamp(state.scalingFactor + 0.1, 0, 1), + }; + } else if (action.type === 'userClickedZoomOut') { + return { + ...state, + scalingFactor: clamp(state.scalingFactor - 0.1, 0, 1), + }; } else if (action.type === 'userZoomed') { const stateWithNewScaling: CameraState = { ...state, @@ -100,6 +110,32 @@ export const cameraReducer: Reducer = ( } else { return state; } + } else if (action.type === 'userClickedPanControl') { + const panDirection = action.payload; + /** + * Delta amount will be in the range of 20 -> 40 depending on the scalingFactor + */ + const deltaAmount = (1 + state.scalingFactor) * 20; + let delta: Vector2; + if (panDirection === 'north') { + delta = [0, -deltaAmount]; + } else if (panDirection === 'south') { + delta = [0, deltaAmount]; + } else if (panDirection === 'east') { + delta = [-deltaAmount, 0]; + } else if (panDirection === 'west') { + delta = [deltaAmount, 0]; + } else { + delta = [0, 0]; + } + + return { + ...state, + translationNotCountingCurrentPanning: [ + state.translationNotCountingCurrentPanning[0] + delta[0], + state.translationNotCountingCurrentPanning[1] + delta[1], + ], + }; } else if (action.type === 'userSetRasterSize') { /** * Handle resizes of the Resolver component. We need to know the size in order to convert between screen diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts index a7b0bbf66052d0..53ffe6dd073fa6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts @@ -182,6 +182,13 @@ export const scale = (state: CameraState): Vector2 => { return [value, value]; }; +/** + * Scales the coordinate system, used for zooming. Should always be between 0 and 1 + */ +export const scalingFactor = (state: CameraState): CameraState['scalingFactor'] => { + return state.scalingFactor; +}; + /** * Whether or not the user is current panning the map. */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts index 4b0915282e86f2..abc113d5999ffe 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts @@ -8,7 +8,7 @@ import { CameraAction } from './action'; import { cameraReducer } from './reducer'; import { createStore, Store } from 'redux'; import { CameraState, AABB } from '../../types'; -import { viewableBoundingBox, inverseProjectionMatrix } from './selectors'; +import { viewableBoundingBox, inverseProjectionMatrix, scalingFactor } from './selectors'; import { expectVectorsToBeClose } from './test_helpers'; import { scaleToZoom } from './scale_to_zoom'; import { applyMatrix3 } from '../../lib/vector2'; @@ -151,4 +151,29 @@ describe('zooming', () => { }); }); }); + describe('zoom controls', () => { + let previousScalingFactor: CameraState['scalingFactor']; + describe('when user clicks on zoom in button', () => { + beforeEach(() => { + previousScalingFactor = scalingFactor(store.getState()); + const action: CameraAction = { type: 'userClickedZoomIn' }; + store.dispatch(action); + }); + it('the scaling factor should increase by 0.1 units', () => { + const actual = scalingFactor(store.getState()); + expect(actual).toEqual(previousScalingFactor + 0.1); + }); + }); + describe('when user clicks on zoom out button', () => { + beforeEach(() => { + previousScalingFactor = scalingFactor(store.getState()); + const action: CameraAction = { type: 'userClickedZoomOut' }; + store.dispatch(action); + }); + it('the scaling factor should decrease by 0.1 units', () => { + const actual = scalingFactor(store.getState()); + expect(actual).toEqual(previousScalingFactor - 0.1); + }); + }); + }); }); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts index 30adf172030969..eb1c1fec369957 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts @@ -31,6 +31,11 @@ export const inverseProjectionMatrix = composeSelectors( */ export const scale = composeSelectors(cameraStateSelector, cameraSelectors.scale); +/** + * Scales the coordinate system, used for zooming. Should always be between 0 and 1 + */ +export const scalingFactor = composeSelectors(cameraStateSelector, cameraSelectors.scalingFactor); + /** * Whether or not the user is current panning the map. */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index eae9ebf9ee9a63..f2ae9785446f7a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -182,3 +182,8 @@ export type ProcessWithWidthMetadata = { firstChildWidth: null; } ); + +/** + * String that represents the direction in which Resolver can be panned + */ +export type PanDirection = 'north' | 'south' | 'east' | 'west'; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/graph_controls.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/graph_controls.tsx new file mode 100644 index 00000000000000..3170f8bdf867ea --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/graph_controls.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import styled from 'styled-components'; +import { EuiRange, EuiPanel, EuiIcon } from '@elastic/eui'; +import { useSelector, useDispatch } from 'react-redux'; +import { ResolverAction, PanDirection } from '../types'; +import * as selectors from '../store/selectors'; + +/** + * Controls for zooming, panning, and centering in Resolver + */ +export const GraphControls = styled( + React.memo( + ({ + className, + }: { + /** + * A className string provided by `styled` + */ + className?: string; + }) => { + const dispatch: (action: ResolverAction) => unknown = useDispatch(); + const scalingFactor = useSelector(selectors.scalingFactor); + + const handleZoomAmountChange = useCallback( + (event: React.ChangeEvent | React.MouseEvent) => { + const valueAsNumber = parseFloat( + (event as React.ChangeEvent).target.value + ); + if (isNaN(valueAsNumber) === false) { + dispatch({ + type: 'userSetZoomLevel', + payload: valueAsNumber, + }); + } + }, + [dispatch] + ); + + const handleCenterClick = useCallback(() => { + dispatch({ + type: 'userSetPositionOfCamera', + payload: [0, 0], + }); + }, [dispatch]); + + const handleZoomOutClick = useCallback(() => { + dispatch({ + type: 'userClickedZoomOut', + }); + }, [dispatch]); + + const handleZoomInClick = useCallback(() => { + dispatch({ + type: 'userClickedZoomIn', + }); + }, [dispatch]); + + const handlePanClick = (panDirection: PanDirection) => { + return () => { + dispatch({ + type: 'userClickedPanControl', + payload: panDirection, + }); + }; + }; + + return ( +
+ +
+ +
+
+ + + +
+
+ +
+
+ + + + + +
+ ); + } + ) +)` + position: absolute; + top: 5px; + left: 5px; + z-index: 1; + background-color: #d4d4d4; + color: #333333; + + .zoom-controls { + display: flex; + flex-direction: column; + align-items: center; + padding: 5px 0px; + + .zoom-slider { + width: 20px; + height: 150px; + margin: 5px 0px 2px 0px; + + input[type='range'] { + width: 150px; + height: 20px; + transform-origin: 75px 75px; + transform: rotate(-90deg); + } + } + } + .panning-controls { + text-align: center; + } +`; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index d252988d0cdcc0..a69504e3a5db12 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -14,6 +14,7 @@ import { useAutoUpdatingClientRect } from './use_autoupdating_client_rect'; import { useNonPassiveWheelHandler } from './use_nonpassive_wheel_handler'; import { ProcessEventDot } from './process_event_dot'; import { EdgeLine } from './edge_line'; +import { GraphControls } from './graph_controls'; export const AppRoot = React.memo(({ store }: { store: Store }) => { return ( @@ -138,18 +139,16 @@ const Resolver = styled( useNonPassiveWheelHandler(handleWheel, ref); return ( -
- {Array.from(processNodePositions).map(([processEvent, position], index) => ( - - ))} - {edgeLineSegments.map(([startPosition, endPosition], index) => ( - - ))} +
+ +
+ {Array.from(processNodePositions).map(([processEvent, position], index) => ( + + ))} + {edgeLineSegments.map(([startPosition, endPosition], index) => ( + + ))} +
); }) @@ -167,4 +166,9 @@ const Resolver = styled( * Prevent partially visible components from showing up outside the bounds of Resolver. */ overflow: hidden; + + .resolver-graph { + display: flex; + flex-grow: 1; + } `; From 884560806cd39410b292886549afa3061dd69c7c Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Tue, 21 Jan 2020 16:33:53 -0500 Subject: [PATCH 35/59] Retain pinned filters when loading and clearing saved queries (#54307) When we originally implemented Saved Queries we had them overwrite pinned filters on load and on clear. This caused the issue in #53258. If you have a saved query loaded in Discover for example and you navigate to a different app and then back to Discover, that saved query will get get reloaded since app state is retained when navigating back and forth between apps. If you created a pinned filter in between visits to Discover, it will get removed when the saved query is reloaded. This issue made me reconsider our previous decision. I think pinned filters should not be affected by loading or clearing a saved query, since they are pinned they should only be removed if the user explicitly asks for it. This solves the reported issue and I also think it makes the UI more intuitive. --- .../np_ready/dashboard_app_controller.tsx | 17 ++++++++------ .../discover/np_ready/angular/discover.js | 6 +++-- .../visualize/np_ready/editor/editor.js | 6 +++-- .../lens/public/app_plugin/app.test.tsx | 22 +++++++++++++++---- .../plugins/lens/public/app_plugin/app.tsx | 8 ++++--- .../maps/public/angular/map_controller.js | 10 ++++++--- 6 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 7da32ac97126f4..4da445166df45d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -480,18 +480,21 @@ export class DashboardAppController { language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }, - [] + queryFilter.getGlobalFilters() ); // Making this method sync broke the updates. // Temporary fix, until we fix the complex state in this file. - setTimeout(queryFilter.removeAll, 0); + setTimeout(() => { + queryFilter.setFilters(queryFilter.getGlobalFilters()); + }, 0); }; const updateStateFromSavedQuery = (savedQuery: SavedQuery) => { - dashboardStateManager.applyFilters( - savedQuery.attributes.query, - savedQuery.attributes.filters || [] - ); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = queryFilter.getGlobalFilters(); + const allFilters = [...globalFilters, ...savedQueryFilters]; + + dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters); if (savedQuery.attributes.timefilter) { timefilter.setTime({ from: savedQuery.attributes.timefilter.from, @@ -504,7 +507,7 @@ export class DashboardAppController { // Making this method sync broke the updates. // Temporary fix, until we fix the complex state in this file. setTimeout(() => { - queryFilter.setFilters(savedQuery.attributes.filters || []); + queryFilter.setFilters(allFilters); }, 0); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 2bf554860f7434..cde0b5d27bdc54 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -1000,7 +1000,7 @@ function discoverController( query: '', language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }; - filterManager.removeAll(); + filterManager.setFilters(filterManager.getGlobalFilters()); $state.save(); $scope.fetch(); }; @@ -1008,7 +1008,9 @@ function discoverController( const updateStateFromSavedQuery = savedQuery => { $state.query = savedQuery.attributes.query; $state.save(); - filterManager.setFilters(savedQuery.attributes.filters || []); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = filterManager.getGlobalFilters(); + filterManager.setFilters([...globalFilters, ...savedQueryFilters]); if (savedQuery.attributes.timefilter) { timefilter.setTime({ diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index 0e085b8553bf02..297e92b5277310 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -501,7 +501,7 @@ function VisualizeAppController( language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), }; - queryFilter.removeAll(); + queryFilter.setFilters(queryFilter.getGlobalFilters()); $state.save(); $scope.fetch(); }; @@ -510,7 +510,9 @@ function VisualizeAppController( $state.query = savedQuery.attributes.query; $state.save(); - queryFilter.setFilters(savedQuery.attributes.filters || []); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = queryFilter.getGlobalFilters(); + queryFilter.setFilters([...globalFilters, ...savedQueryFilters]); if (savedQuery.attributes.timefilter) { timefilter.setTime({ diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 794128832461ba..80a7ceb61c324c 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -12,7 +12,12 @@ import { EditorFrameInstance } from '../types'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + FilterManager, + IFieldType, + IIndexPattern, +} from '../../../../../../src/plugins/data/public'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); @@ -60,6 +65,10 @@ function createMockFilterManager() { subscriber(); }, getFilters: () => filters, + getGlobalFilters: () => { + // @ts-ignore + return filters.filter(esFilters.isFilterPinned); + }, removeAll: () => { filters = []; subscriber(); @@ -821,7 +830,7 @@ describe('Lens App', () => { ); }); - it('clears all existing filters when the active saved query is cleared', () => { + it('clears all existing unpinned filters when the active saved query is cleared', () => { const args = makeDefaultArgs(); args.editorFrame = frame; @@ -834,8 +843,13 @@ describe('Lens App', () => { const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; + const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - args.data.query.filterManager.setFilters([esFilters.buildExistsFilter(field, indexPattern)]); + const unpinned = esFilters.buildExistsFilter(field, indexPattern); + const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); + FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); + + args.data.query.filterManager.setFilters([pinned, unpinned]); instance.update(); instance.find(TopNavMenu).prop('onClearSavedQuery')!(); @@ -844,7 +858,7 @@ describe('Lens App', () => { expect(frame.mount).toHaveBeenLastCalledWith( expect.any(Element), expect.objectContaining({ - filters: [], + filters: [pinned], }) ); }); diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index f33cd41f46a115..35e45af6a3d689 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -239,7 +239,9 @@ export function App({ setState(s => ({ ...s, savedQuery })); }} onSavedQueryUpdated={savedQuery => { - data.query.filterManager.setFilters(savedQuery.attributes.filters || state.filters); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = data.query.filterManager.getGlobalFilters(); + data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); setState(s => ({ ...s, savedQuery: { ...savedQuery }, // Shallow query for reference issues @@ -252,11 +254,11 @@ export function App({ })); }} onClearSavedQuery={() => { - data.query.filterManager.removeAll(); + data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); setState(s => ({ ...s, savedQuery: undefined, - filters: [], + filters: data.query.filterManager.getGlobalFilters(), query: { query: '', language: diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index eec97dc5c71e9c..ece775f5a7e25a 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -153,7 +153,7 @@ app.controller( delete $scope.savedQuery; delete $state.savedQuery; onQueryChange({ - filters: [], + filters: filterManager.getGlobalFilters(), query: { query: '', language: localStorage.get('kibana.userQueryLanguage'), @@ -162,6 +162,10 @@ app.controller( }; function updateStateFromSavedQuery(savedQuery) { + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = filterManager.getGlobalFilters(); + const allFilters = [...savedQueryFilters, ...globalFilters]; + if (savedQuery.attributes.timefilter) { if (savedQuery.attributes.timefilter.refreshInterval) { $scope.onRefreshChange({ @@ -170,13 +174,13 @@ app.controller( }); } onQueryChange({ - filters: savedQuery.attributes.filters || [], + filters: allFilters, query: savedQuery.attributes.query, time: savedQuery.attributes.timefilter, }); } else { onQueryChange({ - filters: savedQuery.attributes.filters || [], + filters: allFilters, query: savedQuery.attributes.query, }); } From 391c348a977063b28c66a5e07fd4381b21341a12 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Jan 2020 16:45:40 -0500 Subject: [PATCH 36/59] remove incorrect config (#55427) --- docs/setup/settings.asciidoc | 22 ++++++++++--------- .../resources/bin/kibana-docker | 1 - 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 535ad169782170..6fdb81c780a7d7 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -229,23 +229,25 @@ Kibana, the server needs to be CORS-enabled so Kibana can download the file. The following example shows a valid regionmap configuration. + -- - map.regionmap: + map includeElasticMapsService: false - layers: - - name: "Departments of France" - url: "http://my.cors.enabled.server.org/france_departements.geojson" - attribution: "INRAP" - fields: - - name: "department" + regionmap: + layers: + - name: "Departments of France" + url: "http://my.cors.enabled.server.org/france_departements.geojson" + attribution: "INRAP" + fields: + - name: "department" description: "Full department name" - - name: "INSEE" + - name: "INSEE" description: "INSEE numeric identifier" -- -[[regionmap-ES-map]]`map.regionmap.includeElasticMapsService:`:: Turns on or off +[[regionmap-ES-map]]`map.includeElasticMapsService:`:: Turns on or off whether layers from the Elastic Maps Service should be included in the vector layer option list. Supported on Elastic Cloud Enterprise. By turning this off, -only the layers that are configured here will be included. The default is `true`. +only the layers that are configured here will be included. The default is `true`. +This also affects whether tile-service from the Elastic Maps Service will be available. [[regionmap-attribution]]`map.regionmap.layers[].attribution:`:: Optional. References the originating source of the geojson file. Supported on {ece}. diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 31a5a4c13a4be2..de54967504b0e7 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -59,7 +59,6 @@ kibana_vars=( path.data pid.file regionmap - regionmap.includeElasticMapsService server.basePath server.customResponseHeaders server.defaultRoute From 31d3821598a1cb88613b0f9f92921ea935e23358 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 21 Jan 2020 15:28:49 -0700 Subject: [PATCH 37/59] [SIEM][Detection Engine] Critical blocker, adds need REST prefix for cloud ## Summary * Adds needed `/` to the beginning of cloud requests from Kibana -> ES ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ ~~- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~~ ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../server/lib/detection_engine/index/create_bootstrap_index.ts | 2 +- .../siem/server/lib/detection_engine/index/delete_policy.ts | 2 +- .../siem/server/lib/detection_engine/index/get_policy_exists.ts | 2 +- .../siem/server/lib/detection_engine/index/set_policy.ts | 2 +- .../server/lib/detection_engine/privileges/read_privileges.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts index d7cb922b5b6c38..dff6e7136bff2b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts @@ -19,7 +19,7 @@ export const createBootstrapIndex = async ( index: string ): Promise => { return callWithRequest('transport.request', { - path: `${index}-000001`, + path: `/${index}-000001`, method: 'PUT', body: { aliases: { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts index 92003f165d9962..aa31c427ec84f8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts @@ -11,7 +11,7 @@ export const deletePolicy = async ( policy: string ): Promise => { return callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'DELETE', }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts index 7541c4217b387e..d5ab1a10180c0e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts @@ -12,7 +12,7 @@ export const getPolicyExists = async ( ): Promise => { try { await callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'GET', }); // Return true that there exists a policy which is not 404 or some error diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts index 115f0af75898c7..fae28bab749cad 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts @@ -12,7 +12,7 @@ export const setPolicy = async ( body: unknown ): Promise => { return callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'PUT', body, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts index 3b84075b9e435a..a93be40738e570 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts @@ -11,7 +11,7 @@ export const readPrivileges = async ( index: string ): Promise => { return callWithRequest('transport.request', { - path: `_security/user/_has_privileges`, + path: '/_security/user/_has_privileges', method: 'POST', body: { cluster: [ From db1a64da761e92b8651ce975ff152ca263ba6088 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 21 Jan 2020 15:29:43 -0700 Subject: [PATCH 38/59] [SIEM][Detection Engine] Fixes critical blocker where signals on signals are not operating ## Summary This fixes halting, infinite creation of signals, and cyclic issues with signals when they are reflected on their own index. Without this fix, you could get a user who looks back at a signals index as both their input and output index and forever generates new signals forever and ever and ever until the heath death of the universe. * Changes the data structure to support parent and ancestors * Adds a check for the parent and ancestors * Adds README.md and in-depth testing of cyclic concepts * Adds README.md and in-depth testing of depth levels of signal concepts * Added unit tests for both use cases * Removed extra console.log statement found in the code base Follow the two README.md's included for testing and explanation of how it works. See `test_cases/signals_on_signals/depth_test` See `test_cases/signals_on_signals/halting_test` ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../scripts/convert_saved_search_to_rules.js | 1 - .../server/lib/detection_engine/README.md | 2 +- .../routes/__mocks__/request_responses.ts | 2 +- .../routes/index/signals_mapping.json | 6 + .../import/multiple_ruleid_queries.ndjson | 4 +- .../scripts/rules/test_cases/README.md | 12 - .../multiple_ruleid_queries_corrupted.ndjson | 6 +- .../filter_with_empty_query.json | 0 .../{ => queries}/filter_without_query.json | 0 .../query_filter_ui_meatadata_lucene.json | 0 .../query_filter_ui_metadata.json | 0 .../{ => queries}/query_with_errors.json | 0 .../saved_query_ui_meta_empty_query.json | 0 .../signals_on_signals/depth_test/README.md | 367 +++++++++++++++++ .../depth_test/query_single_id.json | 12 + .../depth_test/signal_on_signal_depth_1.json | 13 + .../depth_test/signal_on_signal_depth_2.json | 13 + .../signals_on_signals/halting_test/README.md | 375 ++++++++++++++++++ .../halting_test/query_single_id.json | 12 + .../halting_test/signal_on_signal.json | 13 + .../signals/__mocks__/es_results.ts | 40 +- .../signals/build_bulk_body.test.ts | 61 ++- .../signals/build_signal.test.ts | 156 +++++++- .../detection_engine/signals/build_signal.ts | 45 ++- .../signals/search_after_bulk_create.test.ts | 11 +- .../signals/signal_rule_alert_type.ts | 2 +- .../signals/single_bulk_create.test.ts | 82 +++- .../signals/single_bulk_create.ts | 24 ++ .../lib/detection_engine/signals/types.ts | 23 +- 29 files changed, 1226 insertions(+), 56 deletions(-) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => imports}/multiple_ruleid_queries_corrupted.ndjson (55%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/filter_with_empty_query.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/filter_without_query.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/query_filter_ui_meatadata_lucene.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/query_filter_ui_metadata.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/query_with_errors.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/{ => queries}/saved_query_ui_meta_empty_query.json (100%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json diff --git a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js index 4243e67ca1320c..233d4dd7de721e 100644 --- a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js +++ b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js @@ -114,7 +114,6 @@ async function main() { ); return [...accum, parsedLine]; } catch (err) { - console.log('error parsing a line in this file:', json, line); return accum; } }, []); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md index 7c22d6334a2d13..1d33466a458d23 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md @@ -94,7 +94,7 @@ You should see the new rules created like so: "interval": "5m", "rule_id": "rule-1", "language": "kuery", - "output_index": ".siem-signals-frank-hassanabad", + "output_index": ".siem-signals-some-name", "max_signals": 100, "risk_score": 1, "name": "Detect Root/Admin Users", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a84fcb64d9ff7c..582def5ed7bdfa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -371,7 +371,7 @@ export const getMockPrivileges = () => ({ create_snapshot: true, }, index: { - '.siem-signals-frank-hassanabad-test-space': { + '.siem-signals-test-space': { all: false, manage_ilm: true, read: false, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json index 00ae5b1f7426bf..4f3ba768b17b09 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json @@ -5,6 +5,9 @@ "properties": { "parent": { "properties": { + "rule": { + "type": "keyword" + }, "index": { "type": "keyword" }, @@ -19,6 +22,9 @@ } } }, + "ancestors": { + "type": "object" + }, "rule": { "properties": { "id": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson index a9de8b1e475a36..4c45ac7a1b38b8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson @@ -1,3 +1,3 @@ -{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} {"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md index 8b6508c64dc5c5..38139f783c245d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md @@ -4,15 +4,3 @@ use these type of rule based messages when writing pure REST API calls. These me more of what you would see "behind the scenes" when you are using Kibana UI which can create rules with additional "meta" data or other implementation details that aren't really a concern for a regular REST API user. - -To post all of them to see in the UI, with the scripts folder as your current working directory: - -```sh -./post_rule.sh ./rules/test_cases/*.json -``` - -To post only one at a time: - -```sh -./post_rule.sh ./rules/test_cases/.json -``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson similarity index 55% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson index 94fc36ef6f7bfa..744bd1e078a41c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson @@ -1,4 +1,4 @@ -{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1}, -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-3","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1}, +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-3","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} {"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_with_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_with_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_without_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_without_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_meatadata_lucene.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_meatadata_lucene.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_metadata.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_metadata.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_with_errors.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_with_errors.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_with_errors.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_with_errors.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/saved_query_ui_meta_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/saved_query_ui_meta_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md new file mode 100644 index 00000000000000..ff3e9a8cf09486 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md @@ -0,0 +1,367 @@ +This is a depth test which allows users and UI's to create "funnels" of information. You can funnel your data into smaller +and smaller data sets using this. For example, you might have 1,000k of events but generate only 100k of signals off +of those events. However, you then want to generate signals on top of signals that are only 10k. Likewise you might want +signals on top of signals on top of signals to generate only 1k. + +``` +events from indexes might be 1,000k (no depth) +signals -> events would be less such as 100k +signals -> signals -> events would be even less (such as 10k) +signals -> signals -> events would be even less (such as 1k) +``` + +This folder contains a rule called + +```sh +query_single_id.json +``` + +which will write a single signal document into the signals index by searching for a single document `"query": "_id: o8G7vm8BvLT8jmu5B1-M"` . Then another rule called + +```sh +signal_on_signal_depth_1.json +``` + +which has this key part of its query: `"query": "signal.parent.depth: 1 and _id: *"` which will only create signals +from all signals that point directly to an event (signal -> event). + +Then a second rule called + +```sh +signal_on_signal_depth_2.json +``` + +which will only create signals from all signals that point directly to another signal (signal -> signal) with this query + +```json +"query": "signal.parent.depth: 2 and _id: *" +``` + +## Setup + +You should first get a valid `_id` from the system from the last 24 hours by running any query within timeline +or in the system and copying its `_id`. Once you have that `_id` add it to `query_single_id.json`. For example if you have found an `_id` +in the last 24 hours of `sQevtW8BvLT8jmu5l0TA` add it to `query_single_id.json` under the key `query` like so: + +```json +"query": "_id: sQevtW8BvLT8jmu5l0TA", +``` + +Then get your current signal index: + +```json +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And edit the `signal_on_signal.json` and add that index to the key of `index` so we are running that rule against the signals index: + +```json +"index": ".siem-signals-default" +``` + +Next you want to clear out all of your signals and all rules: + +```sh +./hard_reset.sh +``` + +Finally, insert and start the first the query like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/query_single_id.json +``` + +Wait 30+ seconds to ensure that the single record shows up in your signals index. You can use dev tools in Kibana +to see this by first getting your configured signals index by running: + +```ts +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And then you can query against that: + +```ts +GET .siem-signals-default/_search +``` + +Check your parent section of the signal and you will see something like this: + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The parent and ancestors structure is defined as: + +``` +rule -> The id of the rule. You can view the rule by ./get_rule_by_rule_id.sh ded57b36-9c4e-4ee4-805d-be4e92033e41 +id -> The original _id of the document +type -> The type of the document, it will be either event or signal +index -> The original location of the index +depth -> The depth of this signal. It will be at least 1 to indicate it is a signal generated from a event. Otherwise 2 or more to indicate a signal on signal and what depth we are at +ancestors -> An array tracking all of the parents of this particular signal. As depth increases this will too. +``` + +This is indicating that you have a single parent of an event from the signal (signal -> event) and this document has a single +ancestor of that event. Each 30 seconds that goes it will use de-duplication techniques to ensure that this signal is not re-inserted. If after +each 30 seconds you DO SEE multiple signals then the bug is a de-duplication bug and a critical bug. If you ever see a duplicate rule in the +ancestors array then that is another CRITICAL bug which needs to be fixed. + +After this is ensured, the next step is to run a single signal on top of a signal by posting once + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json +``` + +Notice in `signal_on_signal_depth_1.json` we do NOT have a `rule_id` set. This is intentional and is to make it so we can test N rules +running in the system which are generating signals on top of signals. After 30 seconds have gone by you should see that you now have two +documents in the signals index. The first signal is our original (signal -> event) document with a rule id: + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +and the second document is a signal on top of a signal like so: + +```json +"parent" : { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Notice that the depth indicates it is at level 2 and its parent is that of a signal. Also notice that the ancestors is an array of size 2 +indicating that this signal terminates at an event. Each and every signal ancestors array should terminate at an event and should ONLY contain 1 +event and NEVER 2 or more events. After 30+ seconds you should NOT see any new documents being created and you should be stable +at 2. Otherwise we have AND/OR a de-duplication issue, signal on signal issue. + +Now, post this same rule a second time as a second instance which is going to run against these two documents. + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json +``` + +If you were to look at the number of rules you have: + +```sh +./find_rules.sh +``` + +You should see that you have 3 rules running concurrently at this point. Write down the `id` to keep track of them + +- 1 event rule which is always finding the same event continuously (id: 74e0dd0c-4609-416f-b65e-90f8b2564612) +- 1 signal rule which is finding ALL signals at depth 1 (id: 1d3b3735-66ef-4e53-b7f5-4340026cc40c) +- 1 signal rule which is finding ALL signals at depth 1 (id: c93ddb57-e7e9-4973-9886-72ddefb4d22e) + +The expected behavior is that eventually you will get 3 total documents but not additional ones after 1+ minutes. These will be: + +The original event rule 74e0dd0c-4609-416f-b65e-90f8b2564612 (event -> signal) + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The first signal to signal rule 1d3b3735-66ef-4e53-b7f5-4340026cc40c (signal -> event) + +```json +"parent" : { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Then our second signal to signal rule c93ddb57-e7e9-4973-9886-72ddefb4d22e (signal -> event) which finds the same thing as the first +signal to signal + +```json +"parent" : { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +We should be able to post this depth level as many times as we want and get only 1 new document each time. If we decide though to +post `signal_on_signal_depth_2.json` like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json +``` + +The expectation is that a document for each of the previous depth 1 documents would be produced. Since we have 2 instances of +depth 1 rules running then the signals at depth 2 will produce two new ones and those two will look like so: + +```json +"parent" : { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "365236ce5e77770508152403b4e16613f407ae4b1a135a450dcfec427f2a3231", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "365236ce5e77770508152403b4e16613f407ae4b1a135a450dcfec427f2a3231", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +```json +"parent" : { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "e8b1f1adb40fd642fa524dea89ef94232e67b05e99fb0b2683f1e47e90b759fb", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "e8b1f1adb40fd642fa524dea89ef94232e67b05e99fb0b2683f1e47e90b759fb", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +The total number of documents should be 5 at this point. If you were to post this same rule a second time to get a second instance +running you will end up with 7 documents as it will only re-report the first 2 and not interfere with the other rules. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json new file mode 100644 index 00000000000000..dc05c656d7cf1f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json @@ -0,0 +1,12 @@ +{ + "name": "Queries single id", + "description": "Finds only one id below to create a single signal. Change the query to your exact _id you want to test with", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: o8G7vm8BvLT8jmu5B1-M", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json new file mode 100644 index 00000000000000..fb13413a02791b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1 Depth 1", + "description": "Example Signal on Signal where it reports everything as a signal at depth 1", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "signal.parent.depth: 1 and _id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json new file mode 100644 index 00000000000000..c1b7594653ec73 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1 Depth 2", + "description": "Example Signal on Signal where it reports everything as a signal at Depth 2", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "signal.parent.depth: 2 and _id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md new file mode 100644 index 00000000000000..7895e579de3a66 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md @@ -0,0 +1,375 @@ +This test is to ensure that signals will "halt" eventually when they are run against themselves. This isn't how anyone should setup +signals on signals but rather how we will eventually "halt" given the worst case situations where users are running signals on top of signals +that are duplicates of each other and going very far back in time. + +It contains a rule called + +```sh +query_single_id.json +``` + +which will write a single signal document into the signals index by searching for a single document `"query": "_id: o8G7vm8BvLT8jmu5B1-M"` . Then another rule called + +```sh +signal_on_signal.json +``` + +which will always generate a signal for EVERY single document it sees `"query": "_id: *"` + +## Setup + +You should first get a valid `_id` from the system from the last 24 hours by running any query within timeline +or in the system and copying its `_id`. Once you have that `_id` add it to `query_single_id.json`. For example if you have found an `_id` +in the last 24 hours of `sQevtW8BvLT8jmu5l0TA` add it to `query_single_id.json` under the key `query` like so: + +```json +"query": "_id: sQevtW8BvLT8jmu5l0TA", +``` + +Then get your current signal index: + +```json +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And edit the `signal_on_signal.json` and add that index to the key of `index` so we are running that rule against the signals index: + +```json +"index": ".siem-signals-default" +``` + +Next you want to clear out all of your signals and all rules: + +```sh +./hard_reset.sh +``` + +Finally, insert and start the first the query like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/query_single_id.json +``` + +Wait 30+ seconds to ensure that the single record shows up in your signals index. You can use dev tools in Kibana +to see this by first getting your configured signals index by running: + +```ts +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And then you can query against that: + +```ts +GET .siem-signals-default/_search +``` + +Check your parent section of the signal and you will see something like this: + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The parent and ancestors structure is defined as: + +``` +rule -> The id of the rule. You can view the rule by ./get_rule_by_rule_id.sh ded57b36-9c4e-4ee4-805d-be4e92033e41 +id -> The original _id of the document +type -> The type of the document, it will be either event or signal +index -> The original location of the index +depth -> The depth of this signal. It will be at least 1 to indicate it is a signal generated from a event. Otherwise 2 or more to indicate a signal on signal and what depth we are at +ancestors -> An array tracking all of the parents of this particular signal. As depth increases this will too. +``` + +This is indicating that you have a single parent of an event from the signal (signal -> event) and this document has a single +ancestor of that event. Each 30 seconds that goes it will use de-duplication techniques to ensure that this signal is not re-inserted. If after +each 30 seconds you DO SEE multiple signals then the bug is a de-duplication bug and a critical bug. If you ever see a duplicate rule in the +ancestors array then that is another CRITICAL bug which needs to be fixed. + +After this is ensured, the next step is to run a single signal on top of a signal by posting once + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +Notice in `signal_on_signal.json` we do NOT have a `rule_id` set. This is intentional and is to make it so we can test N rules +running in the system which are generating signals on top of signals. After 30 seconds have gone by you should see that you now have two +documents in the signals index. The first signal is our original (signal -> event) document with a rule id: + +(signal -> event) + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +and the second document is a signal on top of a signal like so: + +(signal -> signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Notice that the depth indicates it is at level 2 and its parent is that of a signal. Also notice that the ancestors is an array of size 2 +indicating that this signal terminates at an event. Each and every signal ancestors array should terminate at an event and should ONLY contain 1 +event and NEVER 2 or more events. After 30+ seconds you should NOT see any new documents being created and you should be stable +at 2. Otherwise we have AND/OR a de-duplication issue, signal on signal issue. + +Now, post a second signal that is going to run against these two documents. + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +If you were to look at the number of rules you have: + +```sh +./find_rules.sh +``` + +You should see that you have 3 rules running concurrently at this point. Write down the `id` to keep track of them + +- 1 event rule which is always finding the same event continuously (id: ded57b36-9c4e-4ee4-805d-be4e92033e41) +- 1 signal rule which is finding ALL signals (id: 161fa5b8-0b96-4985-b066-0d99b2bcb904) +- 1 signal rule which is finding ALL signals (id: f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406) + +The expected behavior is that eventually you will get 5 total documents but not additional ones after 1+ minutes. These will be: + +The original event rule ded57b36-9c4e-4ee4-805d-be4e92033e41 (event -> signal) + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The first signal to signal rule 161fa5b8-0b96-4985-b066-0d99b2bcb904 (signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Then our second signal to signal rule f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406 (signal -> event) which finds the same thing as the first +signal to signal + +```json +"parent" : { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +But then f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406 also finds the first signal to signal rule from 161fa5b8-0b96-4985-b066-0d99b2bcb904 +and writes that document out with a depth of 3. (signal -> signal -> event) + +```json +"parent" : { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "c627e5e2576f1b10952c6c57249947e89b6153b763a59fb9e391d0b56be8e7fe", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "c627e5e2576f1b10952c6c57249947e89b6153b763a59fb9e391d0b56be8e7fe", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +Since it wrote that document, the first signal to signal 161fa5b8-0b96-4985-b066-0d99b2bcb904 writes out it found this newly created signal +(signal -> signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "efbe514e8d806a5ef3da7658cfa73961e25befefc84f622e963b45dcac798868", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "efbe514e8d806a5ef3da7658cfa73961e25befefc84f622e963b45dcac798868", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +You will be "halted" at this point as the signal ancestry and de-duplication ensures that we do not report twice on signals and that we do not +create additional duplications. So what happens if we create a 3rd rule which does a signal on a signal? + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +That 3rd signal should find all previous 5 signals and write them out. So that's 5 more. Then each signal will report on those 5 giving a depth of +4 . Grand total will be 16. You can repeat this as many times as you want and should always see an eventual constant stop time of the signals. They should +never keep increasing for this test. + +What about ordering the adding of rules between the query of the document and the signals? This order should not matter and you should get the same +results regardless of if you add the signals -> signals rules first or the query a signal event document first. The same number of documents should also +be outputted. + +Why does it take sometimes several minutes before things become stable? This is because a rule can write a signal back to the index, then another rule +wakes up and writes its document, and the previous rules on next run see this one and creates another chain. This continues until the ancestor detection +part of the code realizes that it is going to create a cyclic if it adds the same rule a second time and you no longer have a DAG (Directed Acyclic Graph) +at which point it terminates. + +What would happen if I changed the rule look-back from `"from": "now-1d"` to something smaller such as `"from": "now-30s"`? Then you won't get the same +number potentially and things are indeterministic because depending on when your rule runs it might find a previous signal and it might not. This is ok +and normal as you are then running signals on signals at the same interval as each other and the rules at the moment. A signal on a signal does not detect +that another signal has written something and it needs to re-run within the same scheduled time period. It also does not detect that another rule has just +written something and does not re-schedule its self to re-run again or against that document. + +How do I then solve the ordering problem event and signal rules writing at the same time? See the `depth_test` folder for more tests around that but you +have a few options. You can run your event rules at 5 minute intervals + 5 minute look back, then your signals rule at a 10 minute interval + 10 minute look +back which will cause it to check the latest run and the previous run for signals to signals depth of 2. For expected signals that should operate at a depth +of 3, you would increase it by another 10 minute look back for a 20 minute interval + 20 minute look back. For level 4, you would increase that to 40 minute +look back and adjust your queries accordingly to check the depth for more efficiency in querying. See `depth_test` for more information. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json new file mode 100644 index 00000000000000..dc05c656d7cf1f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json @@ -0,0 +1,12 @@ +{ + "name": "Queries single id", + "description": "Finds only one id below to create a single signal. Change the query to your exact _id you want to test with", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: o8G7vm8BvLT8jmu5B1-M", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json new file mode 100644 index 00000000000000..0f3a3e5865aa19 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1", + "description": "Example Signal on Signal where it reports everything as a signal", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index ede82a597b238e..9a79b27bac7e94 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -75,7 +75,7 @@ export const sampleDocWithSortId = (someUuid: string = sampleIdGuid): SignalSour sort: ['1234567891111'], }); -export const sampleEmptyDocSearchResults: SignalSearchResponse = { +export const sampleEmptyDocSearchResults = (): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -89,6 +89,44 @@ export const sampleEmptyDocSearchResults: SignalSearchResponse = { max_score: 100, hits: [], }, +}); + +export const sampleDocWithAncestors = (): SignalSearchResponse => { + const sampleDoc = sampleDocNoSortId(); + sampleDoc._source.signal = { + parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + + return { + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total: 0, + max_score: 100, + hits: [sampleDoc], + }, + }; }; export const sampleBulkCreateDuplicateResult = { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts index 90860a817d2703..de11bf6fcc3c15 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -11,6 +11,7 @@ import { sampleIdGuid, } from './__mocks__/es_results'; import { buildBulkBody } from './build_bulk_body'; +import { SignalHit } from './types'; describe('buildBulkBody', () => { beforeEach(() => { @@ -32,18 +33,28 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { kind: 'signal', }, signal: { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -74,7 +85,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with', () => { @@ -99,7 +111,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { action: 'socket_opened', @@ -115,11 +127,21 @@ describe('buildBulkBody', () => { module: 'system', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -150,7 +172,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { @@ -174,7 +197,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { action: 'socket_opened', @@ -189,11 +212,21 @@ describe('buildBulkBody', () => { module: 'system', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -224,7 +257,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { @@ -246,7 +280,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { kind: 'signal', @@ -256,11 +290,21 @@ describe('buildBulkBody', () => { kind: 'event', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -291,6 +335,7 @@ describe('buildBulkBody', () => { created_at: fakeSignalSourceHit.signal.rule?.created_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts index debc619fbf8b21..dcd36ab811e6a1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts @@ -5,9 +5,8 @@ */ import { sampleDocNoSortId, sampleRule } from './__mocks__/es_results'; -import { buildSignal } from './build_signal'; -import { OutputRuleAlertRest } from '../types'; -import { Signal } from './types'; +import { buildSignal, buildAncestor, buildAncestorsSignal } from './build_signal'; +import { Signal, Ancestor } from './types'; describe('buildSignal', () => { beforeEach(() => { @@ -17,15 +16,25 @@ describe('buildSignal', () => { test('it builds a signal as expected without original_event if event does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); delete doc._source.event; - const rule: Partial = sampleRule(); + const rule = sampleRule(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -66,15 +75,25 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule: Partial = sampleRule(); + const rule = sampleRule(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', original_event: { action: 'socket_opened', @@ -112,4 +131,131 @@ describe('buildSignal', () => { }; expect(signal).toEqual(expected); }); + + test('it builds a ancestor correctly if the parent does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule = sampleRule(); + const signal = buildAncestor(doc, rule); + const expected: Ancestor = { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a ancestor correctly if the parent does exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + doc._source.signal = { + parent: { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + const rule = sampleRule(); + const signal = buildAncestor(doc, rule); + const expected: Ancestor = { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'signal', + index: 'myFakeSignalIndex', + depth: 2, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a signal ancestor correctly if the parent does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule = sampleRule(); + const signal = buildAncestorsSignal(doc, rule); + const expected: Ancestor[] = [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ]; + expect(signal).toEqual(expected); + }); + + test('it builds a signal ancestor correctly if the parent does exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + doc._source.signal = { + parent: { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + const rule = sampleRule(); + const signal = buildAncestorsSignal(doc, rule); + const expected: Ancestor[] = [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'signal', + index: 'myFakeSignalIndex', + depth: 2, + }, + ]; + expect(signal).toEqual(expected); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts index 4131c843297ea2..7a63d6831ea97c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts @@ -4,17 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSourceHit, Signal } from './types'; +import { SignalSourceHit, Signal, Ancestor } from './types'; import { OutputRuleAlertRest } from '../types'; -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { - const signal: Signal = { - parent: { +export const buildAncestor = ( + doc: SignalSourceHit, + rule: Partial +): Ancestor => { + const existingSignal = doc._source.signal?.parent; + if (existingSignal != null) { + return { + rule: rule.id != null ? rule.id : '', + id: doc._id, + type: 'signal', + index: doc._index, + depth: existingSignal.depth + 1, + }; + } else { + return { + rule: rule.id != null ? rule.id : '', id: doc._id, type: 'event', index: doc._index, depth: 1, - }, + }; + } +}; + +export const buildAncestorsSignal = ( + doc: SignalSourceHit, + rule: Partial +): Signal['ancestors'] => { + const newAncestor = buildAncestor(doc, rule); + const existingAncestors = doc._source.signal?.ancestors; + if (existingAncestors != null) { + return [...existingAncestors, newAncestor]; + } else { + return [newAncestor]; + } +}; + +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { + const parent = buildAncestor(doc, rule); + const ancestors = buildAncestorsSignal(doc, rule); + const signal: Signal = { + parent, + ancestors, original_time: doc._source['@timestamp'], status: 'open', rule, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index ac6f840943f180..0644d5e467a5a0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -33,7 +33,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); const result = await searchAfterAndBulkCreate({ - someResult: sampleEmptyDocSearchResults, + someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -51,6 +51,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs', async () => { const sampleParams = sampleRuleAlertParams(30); const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); @@ -103,6 +104,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); }); + test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); @@ -126,6 +128,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ @@ -156,6 +159,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ @@ -185,6 +189,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); @@ -217,6 +222,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); @@ -230,7 +236,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(sampleEmptyDocSearchResults); + .mockReturnValueOnce(sampleEmptyDocSearchResults()); const result = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -249,6 +255,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if returns false when singleSearchAfter throws an exception', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 32f2c86914770d..b19e4f48fdb3e4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -246,7 +246,7 @@ export const signalRulesAlertType = ({ // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}", ${err.message}` ); const sDate = new Date().toISOString(); currentStatusSavedObject.attributes.status = 'failed'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index ca4b1d1e8e84a7..d5f11c91a2b7cd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -14,10 +14,11 @@ import { sampleEmptyDocSearchResults, sampleBulkCreateDuplicateResult, sampleBulkCreateErrorResult, + sampleDocWithAncestors, } from './__mocks__/es_results'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { singleBulkCreate } from './single_bulk_create'; +import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create'; export const mockService = { callCluster: jest.fn(), @@ -131,9 +132,9 @@ describe('singleBulkCreate', () => { expect(firstHash).not.toEqual(secondHash); }); }); + test('create successful bulk create', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -144,7 +145,7 @@ describe('singleBulkCreate', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), + someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -159,9 +160,9 @@ describe('singleBulkCreate', () => { }); expect(successfulsingleBulkCreate).toEqual(true); }); + test('create successful bulk create with docs with no versioning', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -172,7 +173,7 @@ describe('singleBulkCreate', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), + someResult: sampleDocSearchResultsNoSortIdNoVersion(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -187,12 +188,12 @@ describe('singleBulkCreate', () => { }); expect(successfulsingleBulkCreate).toEqual(true); }); + test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleEmptyDocSearchResults; mockService.callCluster.mockReturnValue(false); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult, + someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -253,4 +254,71 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(successfulsingleBulkCreate).toEqual(true); }); + + test('filter duplicate rules will return an empty array given an empty array', () => { + const filtered = filterDuplicateRules( + '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + sampleEmptyDocSearchResults() + ); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules will return nothing filtered when the two rule ids do not match with each other', () => { + const filtered = filterDuplicateRules('some id', sampleDocWithAncestors()); + expect(filtered).toEqual([ + { + _index: 'myFakeSignalIndex', + _type: 'doc', + _score: 100, + _version: 1, + _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', + _source: { + someKey: 'someValue', + '@timestamp': 'someTimeStamp', + signal: { + parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }, + }, + }, + ]); + }); + + test('filters duplicate rules will return empty array when the two rule ids match each other', () => { + const filtered = filterDuplicateRules( + '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + sampleDocWithAncestors() + ); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules will return back search responses if they do not have a signal and will NOT filter the source out', () => { + const ancestors = sampleDocWithAncestors(); + ancestors.hits.hits[0]._source = { '@timestamp': 'some timestamp' }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', ancestors); + expect(filtered).toEqual([ + { + _index: 'myFakeSignalIndex', + _type: 'doc', + _score: 100, + _version: 1, + _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', + _source: { '@timestamp': 'some timestamp' }, + }, + ]); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index a6290e57eb2258..cb5de4c974927e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -28,6 +28,28 @@ interface SingleBulkCreateParams { tags: string[]; } +/** + * This is for signals on signals to work correctly. If given a rule id this will check if + * that rule id already exists in the ancestor tree of each signal search response and remove + * those documents so they cannot be created as a signal since we do not want a rule id to + * ever be capable of re-writing the same signal continuously if both the _input_ and _output_ + * of the signals index happens to be the same index. + * @param ruleId The rule id + * @param signalSearchResponse The search response that has all the documents + */ +export const filterDuplicateRules = ( + ruleId: string, + signalSearchResponse: SignalSearchResponse +) => { + return signalSearchResponse.hits.hits.filter(doc => { + if (doc._source.signal == null) { + return true; + } else { + return !doc._source.signal.ancestors.some(ancestor => ancestor.rule === ruleId); + } + }); +}; + // Bulk Index documents. export const singleBulkCreate = async ({ someResult, @@ -43,6 +65,8 @@ export const singleBulkCreate = async ({ enabled, tags, }: SingleBulkCreateParams): Promise => { + someResult.hits.hits = filterDuplicateRules(id, someResult); + if (someResult.hits.hits.length === 0) { return true; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index 544250858a0832..9b7b2b8f1fff9e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -51,11 +51,16 @@ export type SearchTypes = | boolean | boolean[] | object - | object[]; + | object[] + | undefined; export interface SignalSource { [key: string]: SearchTypes; '@timestamp': string; + signal?: { + parent: Ancestor; + ancestors: Ancestor[]; + }; } export interface BulkResponse { @@ -123,14 +128,18 @@ export type SignalRuleAlertTypeDefinition = Omit & { executor: ({ services, params, state }: RuleExecutorOptions) => Promise; }; +export interface Ancestor { + rule: string; + id: string; + type: string; + index: string; + depth: number; +} + export interface Signal { rule: Partial; - parent: { - id: string; - type: string; - index: string; - depth: number; - }; + parent: Ancestor; + ancestors: Ancestor[]; original_time: string; original_event?: SearchTypes; status: 'open' | 'closed'; From b78c1b104237f478cce34d85016198a885d523d5 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 21 Jan 2020 18:00:08 -0500 Subject: [PATCH 39/59] Adds event log for actions and alerting (#45081) initial code for event log see issue https://github.com/elastic/kibana/issues/45083 --- .github/CODEOWNERS | 1 + .../server/lib/action_executor.test.ts | 2 + .../actions/server/lib/action_executor.ts | 70 +++- .../server/lib/task_runner_factory.test.ts | 2 + .../legacy/plugins/actions/server/plugin.ts | 14 + x-pack/legacy/plugins/actions/server/shim.ts | 3 + x-pack/legacy/plugins/actions/server/types.ts | 2 +- x-pack/plugins/event_log/README.md | 299 ++++++++++++++++ x-pack/plugins/event_log/generated/README.md | 4 + .../plugins/event_log/generated/mappings.json | 96 ++++++ x-pack/plugins/event_log/generated/schemas.ts | 95 ++++++ x-pack/plugins/event_log/kibana.json | 8 + .../event_log/scripts/create_schemas.js | 322 ++++++++++++++++++ .../event_log/scripts/lib/line_writer.js | 39 +++ x-pack/plugins/event_log/scripts/mappings.js | 63 ++++ .../event_log/server/es/context.mock.ts | 46 +++ x-pack/plugins/event_log/server/es/context.ts | 99 ++++++ .../event_log/server/es/documents.test.ts | 43 +++ .../plugins/event_log/server/es/documents.ts | 51 +++ x-pack/plugins/event_log/server/es/index.ts | 7 + x-pack/plugins/event_log/server/es/init.ts | 137 ++++++++ .../plugins/event_log/server/es/names.test.ts | 20 ++ x-pack/plugins/event_log/server/es/names.ts | 28 ++ .../server/event_log_service.test.ts | 116 +++++++ .../event_log/server/event_log_service.ts | 85 +++++ .../event_log/server/event_logger.mock.ts | 15 + .../event_log/server/event_logger.test.ts | 75 ++++ .../plugins/event_log/server/event_logger.ts | 177 ++++++++++ x-pack/plugins/event_log/server/index.ts | 13 + .../server/lib/bounded_queue.test.ts | 161 +++++++++ .../event_log/server/lib/bounded_queue.ts | 91 +++++ .../event_log/server/lib/delay.test.ts | 21 ++ x-pack/plugins/event_log/server/lib/delay.ts | 9 + .../event_log/server/lib/ready_signal.test.ts | 40 +++ .../event_log/server/lib/ready_signal.ts | 28 ++ x-pack/plugins/event_log/server/plugin.ts | 102 ++++++ x-pack/plugins/event_log/server/types.ts | 38 +++ .../alerting_api_integration/common/config.ts | 1 + .../common/fixtures/plugins/alerts/index.ts | 4 +- .../spaces_only/tests/actions/execute.ts | 32 ++ 40 files changed, 2443 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/event_log/README.md create mode 100644 x-pack/plugins/event_log/generated/README.md create mode 100644 x-pack/plugins/event_log/generated/mappings.json create mode 100644 x-pack/plugins/event_log/generated/schemas.ts create mode 100644 x-pack/plugins/event_log/kibana.json create mode 100755 x-pack/plugins/event_log/scripts/create_schemas.js create mode 100644 x-pack/plugins/event_log/scripts/lib/line_writer.js create mode 100644 x-pack/plugins/event_log/scripts/mappings.js create mode 100644 x-pack/plugins/event_log/server/es/context.mock.ts create mode 100644 x-pack/plugins/event_log/server/es/context.ts create mode 100644 x-pack/plugins/event_log/server/es/documents.test.ts create mode 100644 x-pack/plugins/event_log/server/es/documents.ts create mode 100644 x-pack/plugins/event_log/server/es/index.ts create mode 100644 x-pack/plugins/event_log/server/es/init.ts create mode 100644 x-pack/plugins/event_log/server/es/names.test.ts create mode 100644 x-pack/plugins/event_log/server/es/names.ts create mode 100644 x-pack/plugins/event_log/server/event_log_service.test.ts create mode 100644 x-pack/plugins/event_log/server/event_log_service.ts create mode 100644 x-pack/plugins/event_log/server/event_logger.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_logger.test.ts create mode 100644 x-pack/plugins/event_log/server/event_logger.ts create mode 100644 x-pack/plugins/event_log/server/index.ts create mode 100644 x-pack/plugins/event_log/server/lib/bounded_queue.test.ts create mode 100644 x-pack/plugins/event_log/server/lib/bounded_queue.ts create mode 100644 x-pack/plugins/event_log/server/lib/delay.test.ts create mode 100644 x-pack/plugins/event_log/server/lib/delay.ts create mode 100644 x-pack/plugins/event_log/server/lib/ready_signal.test.ts create mode 100644 x-pack/plugins/event_log/server/lib/ready_signal.ts create mode 100644 x-pack/plugins/event_log/server/plugin.ts create mode 100644 x-pack/plugins/event_log/server/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7276a726fd6d1f..1fcfd76b22b40d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -129,6 +129,7 @@ # Kibana Alerting Services /x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services /x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services +/x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services /x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 7d9bf20e22aceb..f66ab9b46c8ac0 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -13,6 +13,7 @@ import { savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; +import { createEventLoggerMock } from '../../../../../plugins/event_log/server/event_logger.mock'; const actionExecutor = new ActionExecutor(); const savedObjectsClient = savedObjectsClientMock.create(); @@ -58,6 +59,7 @@ actionExecutor.initialize({ getServices, actionTypeRegistry, encryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts index f0259c739654b2..401470cf0b995b 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts @@ -15,6 +15,8 @@ import { GetServicesFunction, RawAction, } from '../types'; +import { EVENT_LOG_ACTIONS } from '../plugin'; +import { IEvent, IEventLogger } from '../../../../../plugins/event_log/server'; export interface ActionExecutorContext { logger: Logger; @@ -22,6 +24,7 @@ export interface ActionExecutorContext { getServices: GetServicesFunction; encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; actionTypeRegistry: ActionTypeRegistryContract; + eventLogger: IEventLogger; } export interface ExecuteOptions { @@ -54,11 +57,11 @@ export class ActionExecutor { } const { - logger, spaces, getServices, encryptedSavedObjectsPlugin, actionTypeRegistry, + eventLogger, } = this.actionExecutorContext!; const spacesPlugin = spaces(); @@ -89,9 +92,9 @@ export class ActionExecutor { ); const actionType = actionTypeRegistry.get(actionTypeId); - let validatedParams; - let validatedConfig; - let validatedSecrets; + let validatedParams: Record; + let validatedConfig: Record; + let validatedSecrets: Record; try { validatedParams = validateParams(actionType, params); @@ -101,11 +104,16 @@ export class ActionExecutor { return { status: 'error', actionId, message: err.message, retry: false }; } - let result: ActionTypeExecutorResult | null = null; - const actionLabel = `${actionId} - ${actionTypeId} - ${name}`; + const actionLabel = `${actionTypeId}:${actionId}: ${name}`; + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.execute }, + kibana: { namespace, saved_objects: [{ type: 'action', id: actionId }] }, + }; + eventLogger.startTiming(event); + let rawResult: ActionTypeExecutorResult | null | undefined | void; try { - result = await actionType.executor({ + rawResult = await actionType.executor({ actionId, services, params: validatedParams, @@ -113,15 +121,51 @@ export class ActionExecutor { secrets: validatedSecrets, }); } catch (err) { - logger.warn(`action executed unsuccessfully: ${actionLabel} - ${err.message}`); - throw err; + rawResult = { + actionId, + status: 'error', + message: 'an error occurred while running the action executor', + serviceMessage: err.message, + retry: false, + }; } + eventLogger.stopTiming(event); - logger.debug(`action executed successfully: ${actionLabel}`); - - // return basic response if none provided - if (result == null) return { status: 'ok', actionId }; + // allow null-ish return to indicate success + const result = rawResult || { + actionId, + status: 'ok', + }; + + if (result.status === 'ok') { + event.message = `action executed: ${actionLabel}`; + } else if (result.status === 'error') { + event.message = `action execution failure: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = actionErrorToMessage(result); + } else { + event.message = `action execution returned unexpected result: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = 'action execution returned unexpected result'; + } + eventLogger.logEvent(event); return result; } } + +function actionErrorToMessage(result: ActionTypeExecutorResult): string { + let message = result.message || 'unknown error running action'; + + if (result.serviceMessage) { + message = `${message}: ${result.serviceMessage}`; + } + + if (result.retry instanceof Date) { + message = `${message}; retry at ${result.retry.toISOString()}`; + } else if (result.retry) { + message = `${message}; retry: ${JSON.stringify(result.retry)}`; + } + + return message; +} diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index ad2b74da0d7d44..8b828b0ae67bbd 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -16,6 +16,7 @@ import { savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; +import { createEventLoggerMock } from '../../../../../plugins/event_log/server/event_logger.mock'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -62,6 +63,7 @@ const actionExecutorInitializerParams = { actionTypeRegistry, spaces: () => undefined, encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }; const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index ffc4a9cf90e54c..57a95083122685 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -35,6 +35,13 @@ import { } from './routes'; import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; import { LicenseState } from './lib/license_state'; +import { IEventLogger } from '../../../../plugins/event_log/server'; + +const EVENT_LOG_PROVIDER = 'actions'; +export const EVENT_LOG_ACTIONS = { + execute: 'execute', + executeViaHttp: 'execute-via-http', +}; export interface PluginSetupContract { registerType: ActionTypeRegistry['register']; @@ -57,6 +64,7 @@ export class Plugin { private actionExecutor?: ActionExecutor; private defaultKibanaIndex?: string; private licenseState: LicenseState | null = null; + private eventLogger?: IEventLogger; constructor(initializerContext: ActionsPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'actions'); @@ -88,6 +96,11 @@ export class Plugin { attributesToEncrypt: new Set(['apiKey']), }); + plugins.event_log.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); + this.eventLogger = plugins.event_log.getLogger({ + event: { provider: EVENT_LOG_PROVIDER }, + }); + const actionExecutor = new ActionExecutor(); const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); const actionsConfigUtils = getActionsConfigurationUtilities(config as ActionsConfigType); @@ -156,6 +169,7 @@ export class Plugin { getServices, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, actionTypeRegistry: actionTypeRegistry!, + eventLogger: this.eventLogger!, }); taskRunnerFactory!.initialize({ encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index 8077dc67c92c4a..67c7e87b0ded47 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -27,6 +27,7 @@ import { SavedObjectsLegacyService, } from '../../../../../src/core/server'; import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; +import { IEventLogService } from '../../../../plugins/event_log/server'; export interface KibanaConfig { index: string; @@ -67,6 +68,7 @@ export interface ActionsPluginsSetup { xpack_main: XPackMainPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsSetupContract; licensing: LicensingPluginSetup; + event_log: IEventLogService; } export interface ActionsPluginsStart { security?: SecurityPluginStartContract; @@ -126,6 +128,7 @@ export function shim( encryptedSavedObjects: newPlatform.setup.plugins .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, licensing: newPlatform.setup.plugins.licensing as LicensingPluginSetup, + event_log: newPlatform.setup.plugins.event_log as IEventLogService, }; const pluginsStart: ActionsPluginsStart = { diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/legacy/plugins/actions/server/types.ts index 6a6fb7d660cbbd..b11a0db7f333d6 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/legacy/plugins/actions/server/types.ts @@ -63,7 +63,7 @@ export interface ActionTypeExecutorResult { // signature of the action type executor function export type ExecutorType = ( options: ActionTypeExecutorOptions -) => Promise; +) => Promise; interface ValidatorType { validate(value: any): any; diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md new file mode 100644 index 00000000000000..d2d67ffc27d947 --- /dev/null +++ b/x-pack/plugins/event_log/README.md @@ -0,0 +1,299 @@ +# Event Log + +## Overview + +The purpose of this plugin is to provide a way to persist a history of events +occuring in Kibana, initially just for the Make It Action project - alerts +and actions. + + +## Basic Usage - Logging Events + +Follow these steps to use `event_log` in your plugin: + +1. Declare `event_log` as a dependency in `kibana.json`: + +```json +{ + ... + "requiredPlugins": ["event_log"], + ... +} +``` + +2. Register provider / actions, and create your plugin's logger, using service +API provided in the `setup` stage: + +```typescript +... +import { IEventLogger, IEventLogService } from '../../event_log/server'; +interface PluginSetupDependencies { + event_log: IEventLogService; +} +... +public setup(core: CoreSetup, { event_log }: PluginSetupDependencies) { + ... + event_log.registerProviderActions('my-plugin', ['action-1, action-2']); + const eventLogger: IEventLogger = event_log.getLogger({ event: { provider: 'my-plugin' } }); + ... +} +... +``` + +4. To log an event, call `logEvent()` on the `eventLogger` object you created: + +```typescript +... + eventLogger.logEvent({ event: { action: 'action-1' }, tags: ['fe', 'fi', 'fo'] }); +... +``` + + +## Testing + +### Unit tests + +From `kibana-root-folder/x-pack`, run: +```bash +$ node node scripts/jest plugins/event_log +``` + +### API Integration tests + +None yet! + + +## Background + +For the Make It Action alerting / action plugins, we will need a way to +persist data regarding alerts and actions, for UI and investigative purposes. +We're referring to this persisted data as "events", and will be persisted to +a new elasticsearch index referred to as the "event log". + +Example events are actions firing, alerts running their scheduled functions, +alerts scheduling actions to run, etc. + +This functionality will be provided in a new NP plugin `event_log`, and will +provide server-side plugin APIs to write to the event log, and run limited +queries against it. For now, access via HTTP will not be available, due to +security concerns and lack of use cases. + +The current clients for the event log are the actions and alerting plugins, +however the event log currently has nothing specific to them, and is general +purpose, so can be used by any plugin to "log events". + +We currently assume that there may be many events logged, and that (some) customers +may not be interested in "old" events, and so to keep the event log from +consuming too much disk space, we'll set it up with ILM and some kind of +reasonable default policy that can be customized by the user. This implies +also the use of rollver, setting a write index alias upon rollover, and +that searches for events will be done via an ES index pattern / alias to search +across event log indices with a wildcard. + +The shape of the documents indexed into the event log index is a subset of ECS +properties with a few Kibana extensions. Over time the subset is of ECS and +Kibana extensions will likely grow. + +# Basic example + +When an action is executed, an event should be written to the event log. + +Here's a [`kbn-action` command](https://github.com/pmuellr/kbn-action) to +execute a "server log" action (writes a message to the Kibana log): + +```console +$ kbn-action execute 79b4c37e-ef42-4421-a0b0-b536840f930d '{level:info message:hallo}' +{ + "status": "ok" +} +``` + +Here's the event written to the event log index: + +```json +{ + "_index": ".kibana-event-log-000001", + "_type": "_doc", + "_id": "d2CXT20BPOpswQ8vgXp5", + "_score": 1, + "_source": { + "event": { + "provider": "actions", + "action": "execute", + "start": "2019-12-09T21:16:43.424Z", + "end": "2019-12-09T21:16:43.425Z", + "duration": 1000000 + }, + "kibana": { + "namespace": "default", + "saved_objects": [ + { + "type": "action", + "id": "79b4c37e-ef42-4421-a0b0-b536840f930d" + } + ] + }, + "message": "action executed successfully: 79b4c37e-ef42-4421-a0b0-b536840f930d - .server-log - server-log", + "@timestamp": "2019-12-09T21:16:43.425Z", + "ecs": { + "version": "1.3.1" + } + } +} +``` + +The shape of the document written to the index is a subset of [ECS][] with an +extended field of `kibana` with some Kibana-related properties contained within +it. + +The ES mappings for the ECS data, and the config-schema for the ECS data, are +generated by a script, and available here: + +- [`generated/mappings.json`](generated/mappings.json) +- [`generated/schemas.ts`](generated/schemas.ts) + +It's anticipated that these interfaces will grow over time, hopefully adding +more ECS fields but adding Kibana extensions as required. + +Since there are some security concerns with the data, we are currently +restricting access via known saved object ids. That is, you can only query +history records associated with specific saved object ids. + +[ECS]: https://www.elastic.co/guide/en/ecs/current/index.html + + +## API + +```typescript +// IEvent is a TS type generated from the subset of ECS supported + +// the NP plugin returns a service instance from setup() and start() +export interface IEventLogService { + registerProviderActions(provider: string, actions: string[]): void; + isProviderActionRegistered(provider: string, action: string): boolean; + getProviderActions(): Map>; + + getLogger(properties: IEvent): IEventLogger; +} + +export interface IEventLogger { + logEvent(properties: IEvent): void; + startTiming(event: IEvent): void; + stopTiming(event: IEvent): void; +} +``` + +The plugin exposes an `IEventLogService` object to plugins that pre-req it. +Those plugins need to call `registerProviderActions()` to indicate the values +of the `event.provider` and `event.action` values they will be using +when logging events. + +The pre-registration helps in two ways: + +- dealing with misspelled values +- preventing index explosion on those fields + +Once the values are registered, the plugin will get an `IEventLogger` instance +by passing in a set of default properties to be used for all it's logging, +to the `getLogger()` method. For instance, the `actions` plugin creates a +logger with `event.provider` set to `actions`, and provides `event.action` +values when writing actual entries. + +The `IEventLogger` object can be cached at the plugin level and accessed by +any code in the plugin. It has a single method to write an event log entry, +`logEvent()`, which is passed specific properties for the event. + +The final data written is a combination of the data passed to `getLogger()` when +creating the logger, and the data passed on the `logEvent()` call, and then +that result is validated to ensure it's complete and valid. Errors will be +logged to the server log. + +The `logEvent()` method returns no values, and is itself not asynchronous. +It's a "call and forget" kind of thing. The method itself will arrange +to have the ultimate document written to the index asynchronously. It's designed +this way because it's not clear what a client would do with a result from this +method, nor what it would do if the method threw an error. All the error +processing involved with getting the data into the index is handled internally, +and logged to the server log as appropriate. + +The `startTiming()` and `stopTiming()` methods can be used to set the timing +properties `start`, `end`, and `duration` in the event. For example: + +```typescript + const loggedEvent: IEvent = { event: { action: 'foo' } }; + + // sets event.start + eventLogger.startTiming(loggedEvent); + + longRunningFunction(); + + // sets event.end and event.duration + eventLogger.stopTiming(loggedEvent); + + eventLogger.logEvent(loggedEvent); + +``` + +It's anticipated that more "helper" methods like this will be provided in the +future. + + +## Stored data + +The elasticsearch index for the event log will have ILM and rollover support, +as customers may decide to only keep recent event documents, wanting indices +with older event documents deleted, turned cold, frozen, etc. We'll supply +some default values, but customers will be able to tweak these. + +The index template, mappings, config-schema types, etc for the index can +be found in the [generated directory](generated). These files are generated +from a script which takes as input the ECS properties to use, and the Kibana +extensions. + +See [ilm rollover action docs][] for more info on the `is_write_index`, and `index.lifecycle.*` properties. + +[ilm rollover action docs]: https://www.elastic.co/guide/en/elasticsearch/reference/current/_actions.html#ilm-rollover-action + +Of particular note in the `mappings`: + +- all "objects" are `dynamic: 'strict'` implies users can't add new fields +- all the `properties` are indexed + +We may change some of that before releasing. + + +## ILM setup + +We'll want to provide default ILM policy, this seems like a reasonable first +attempt: + +``` +PUT _ilm/policy/event_log_policy +{ + "policy": { + "phases": { + "hot": { + "actions": { + "rollover": { + "max_size": "5GB", + "max_age": "30d" + } + } + } + } + } +} +``` + +This means that ILM would "rollover" the current index, say +`.kibana-event-log-000001` by creating a new index `.kibana-event-log-000002`, +which would "inherit" everything from the index template, and then ILM will +set the write index of the the alias to the new index. This would happen +when the original index grew past 5 GB, or was created more than 30 days ago. + +For more relevant information on ILM, see: +[getting started with ILM doc][] and [write index alias behavior][]: + +[getting started with ILM doc]: https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index-lifecycle-management.html +[write index alias behavior]: https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-rollover-index.html#indices-rollover-is-write-index + diff --git a/x-pack/plugins/event_log/generated/README.md b/x-pack/plugins/event_log/generated/README.md new file mode 100644 index 00000000000000..0361cb12882ab7 --- /dev/null +++ b/x-pack/plugins/event_log/generated/README.md @@ -0,0 +1,4 @@ +The files in this directory were generated by manually running the script +../scripts/create-schemas.js from the root directory of the repository. + +These files should not be edited by hand. diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json new file mode 100644 index 00000000000000..fc1fdb71b0c372 --- /dev/null +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -0,0 +1,96 @@ +{ + "dynamic": "strict", + "properties": { + "@timestamp": { + "type": "date" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "dynamic": "strict" + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + } + }, + "dynamic": "strict" + }, + "error": { + "properties": { + "message": { + "norms": false, + "type": "text" + } + }, + "dynamic": "strict" + }, + "user": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "dynamic": "strict" + }, + "kibana": { + "properties": { + "server_uuid": { + "type": "keyword", + "ignore_above": 1024 + }, + "namespace": { + "type": "keyword", + "ignore_above": 1024 + }, + "saved_objects": { + "properties": { + "store": { + "type": "keyword", + "ignore_above": 1024 + }, + "id": { + "type": "keyword", + "ignore_above": 1024 + }, + "type": { + "type": "keyword", + "ignore_above": 1024 + } + }, + "type": "nested", + "dynamic": "strict" + } + }, + "dynamic": "strict" + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts new file mode 100644 index 00000000000000..a040ede891bfdc --- /dev/null +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// ---------------------------------- WARNING ---------------------------------- +// this file was generated, and should not be edited by hand +// ---------------------------------- WARNING ---------------------------------- + +// provides TypeScript and config-schema interfaces for ECS for use with +// the event log + +import { schema, TypeOf } from '@kbn/config-schema'; + +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; +type DeepPartial = { + [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; +}; + +export const ECS_VERSION = '1.3.1'; + +// types and config-schema describing the es structures +export type IValidatedEvent = TypeOf; +export type IEvent = DeepPartial>; + +export const EventSchema = schema.maybe( + schema.object({ + '@timestamp': ecsDate(), + tags: ecsStringMulti(), + message: ecsString(), + ecs: schema.maybe( + schema.object({ + version: ecsString(), + }) + ), + event: schema.maybe( + schema.object({ + action: ecsString(), + provider: ecsString(), + start: ecsDate(), + duration: ecsNumber(), + end: ecsDate(), + }) + ), + error: schema.maybe( + schema.object({ + message: ecsString(), + }) + ), + user: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + kibana: schema.maybe( + schema.object({ + server_uuid: ecsString(), + namespace: ecsString(), + saved_objects: schema.maybe( + schema.arrayOf( + schema.object({ + store: ecsString(), + id: ecsString(), + type: ecsString(), + }) + ) + ), + }) + ), + }) +); + +function ecsStringMulti() { + return schema.maybe(schema.arrayOf(schema.string())); +} + +function ecsString() { + return schema.maybe(schema.string()); +} + +function ecsNumber() { + return schema.maybe(schema.number()); +} + +function ecsDate() { + return schema.maybe(schema.string({ validate: validateDate })); +} + +const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + +function validateDate(isoDate: string) { + if (ISO_DATE_PATTERN.test(isoDate)) return; + return 'string is not a valid ISO date: ' + isoDate; +} diff --git a/x-pack/plugins/event_log/kibana.json b/x-pack/plugins/event_log/kibana.json new file mode 100644 index 00000000000000..52b68deeffce51 --- /dev/null +++ b/x-pack/plugins/event_log/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "event_log", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["xpack", "event_log"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/event_log/scripts/create_schemas.js b/x-pack/plugins/event_log/scripts/create_schemas.js new file mode 100755 index 00000000000000..6e9ab00d04d1fc --- /dev/null +++ b/x-pack/plugins/event_log/scripts/create_schemas.js @@ -0,0 +1,322 @@ +#!/usr/bin/env node + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const fs = require('fs'); +const path = require('path'); +const lodash = require('lodash'); + +const LineWriter = require('./lib/line_writer'); +const mappings = require('./mappings'); + +const PLUGIN_DIR = path.resolve(path.join(__dirname, '..')); +const ECS_MAPPINGS_FILE = 'generated/elasticsearch/7/template.json'; +const EVENT_LOG_MAPPINGS_FILE = 'generated/mappings.json'; +const EVENT_LOG_CONFIG_SCHEMA_FILE = 'generated/schemas.ts'; + +function main() { + const ecsDir = getEcsDir(); + const ecsVersion = getEcsVersion(ecsDir); + + const ecsMappings = readEcsJSONFile(ecsDir, ECS_MAPPINGS_FILE); + + // add our custom fields + ecsMappings.mappings.properties.kibana = mappings.EcsKibanaExtensionsMappings; + + const exportedProperties = mappings.EcsEventLogProperties; + const multiValuedProperties = new Set(mappings.EcsEventLogMultiValuedProperties); + + const elMappings = getEventLogMappings(ecsMappings, exportedProperties); + + console.log(`generating files in ${PLUGIN_DIR}`); + writeEventLogMappings(elMappings); + writeEventLogConfigSchema(elMappings, ecsVersion, multiValuedProperties); +} + +// return a stripped down version of the ecs schema, with only exportedProperties +function getEventLogMappings(ecsSchema, exportedProperties) { + const result = { mappings: { properties: {} } }; + + // get full list of properties to copy + const leafProperties = exportedProperties.map(replaceDotWithProperties); + + // copy the leaf values of the properties + for (const prop of leafProperties) { + const value = lodash.get(ecsSchema.mappings.properties, prop); + lodash.set(result.mappings.properties, prop, value); + } + + // set the non-leaf values as appropriate + const nonLeafProperties = getNonLeafProperties(exportedProperties).map(replaceDotWithProperties); + for (const prop of nonLeafProperties) { + const ecsValue = lodash.get(ecsSchema.mappings.properties, prop); + const elValue = lodash.get(result.mappings.properties, prop); + + elValue.type = ecsValue.type; + elValue.dynamic = 'strict'; + } + + return result; +} + +// eg, 'ecs.version' -> 'ecs.properties.version' +function replaceDotWithProperties(s) { + return s.replace(/\./g, '.properties.'); +} + +// given an array of property names, return array of object/nested ones +function getNonLeafProperties(propertyNames) { + const result = new Set(); + + for (const propertyName of propertyNames) { + const parts = propertyName.split(/\./g); + if (parts.length <= 1) continue; + parts.pop(); + result.add(parts.join('.')); + } + + return Array.from(result); +} + +function writeEventLogMappings(elSchema) { + // fixObjectTypes(elSchema.mappings); + + const mappings = { + dynamic: 'strict', + properties: elSchema.mappings.properties, + }; + + writeGeneratedFile(EVENT_LOG_MAPPINGS_FILE, JSON.stringify(mappings, null, 4)); + console.log('generated:', EVENT_LOG_MAPPINGS_FILE); +} + +function writeEventLogConfigSchema(elSchema, ecsVersion, multiValuedProperties) { + const lineWriter = LineWriter.createLineWriter(); + + const elSchemaMappings = augmentMappings(elSchema.mappings, multiValuedProperties); + generateSchemaLines(lineWriter, null, elSchemaMappings); + // last line will have an extraneous comma + const schemaLines = lineWriter.getContent().replace(/,$/, ''); + + const contents = getSchemaFileContents(ecsVersion, schemaLines); + const schemaCode = `${contents}\n`; + + writeGeneratedFile(EVENT_LOG_CONFIG_SCHEMA_FILE, schemaCode); + console.log('generated:', EVENT_LOG_CONFIG_SCHEMA_FILE); +} + +const StringTypes = new Set(['string', 'keyword', 'text', 'ip']); +const NumberTypes = new Set(['long', 'integer', 'float']); + +function augmentMappings(mappings, multiValuedProperties) { + // clone the mappings, as we're adding some additional properties + mappings = JSON.parse(JSON.stringify(mappings)); + + for (const prop of multiValuedProperties) { + const fullProp = replaceDotWithProperties(prop); + lodash.set(mappings.properties, `${fullProp}.multiValued`, true); + } + + return mappings; +} + +function generateSchemaLines(lineWriter, prop, mappings) { + const propKey = legalPropertyName(prop); + + if (StringTypes.has(mappings.type)) { + if (mappings.multiValued) { + lineWriter.addLine(`${propKey}: ecsStringMulti(),`); + } else { + lineWriter.addLine(`${propKey}: ecsString(),`); + } + return; + } + + if (NumberTypes.has(mappings.type)) { + lineWriter.addLine(`${propKey}: ecsNumber(),`); + return; + } + + if (mappings.type === 'date') { + lineWriter.addLine(`${propKey}: ecsDate(),`); + return; + } + + // only handling objects for the rest of this function + if (mappings.properties == null) { + logError(`unknown properties to map: ${prop}: ${JSON.stringify(mappings)}`); + } + + // top-level object does not have a property name + if (prop == null) { + lineWriter.addLine(`schema.maybe(`); + lineWriter.indent(); + lineWriter.addLine(`schema.object({`); + } else { + lineWriter.addLine(`${propKey}: schema.maybe(`); + lineWriter.indent(); + if (mappings.type === 'nested') { + lineWriter.addLine(`schema.arrayOf(`); + lineWriter.indent(); + } + lineWriter.addLine(`schema.object({`); + } + + // write the object properties + lineWriter.indent(); + for (const prop of Object.keys(mappings.properties)) { + generateSchemaLines(lineWriter, prop, mappings.properties[prop]); + } + lineWriter.dedent(); + + lineWriter.addLine('})'); + if (mappings.type === 'nested') { + lineWriter.dedent(); + lineWriter.addLine(')'); + } + + lineWriter.dedent(); + lineWriter.addLine('),'); +} + +function legalPropertyName(prop) { + if (prop === '@timestamp') return `'@timestamp'`; + return prop; +} + +function readEcsJSONFile(ecsDir, fileName) { + const contents = readEcsFile(ecsDir, fileName); + + let object; + try { + object = JSON.parse(contents); + } catch (err) { + logError(`ecs file is not JSON: ${fileName}: ${err.message}`); + } + + return object; +} + +function writeGeneratedFile(fileName, contents) { + const genFileName = path.join(PLUGIN_DIR, fileName); + try { + fs.writeFileSync(genFileName, contents); + } catch (err) { + logError(`error writing file: ${genFileName}: ${err.message}`); + } +} + +function readEcsFile(ecsDir, fileName) { + const ecsFile = path.resolve(path.join(ecsDir, fileName)); + + let contents; + try { + contents = fs.readFileSync(ecsFile, { encoding: 'utf8' }); + } catch (err) { + logError(`ecs file not found: ${ecsFile}: ${err.message}`); + } + + return contents; +} + +function getEcsVersion(ecsDir) { + const contents = readEcsFile(ecsDir, 'version').trim(); + if (!contents.match(/^\d+\.\d+\.\d+$/)) { + logError(`ecs is not at a stable version: : ${contents}`); + } + + return contents; +} + +function getEcsDir() { + const ecsDir = path.resolve(path.join(__dirname, '../../../../../ecs')); + + let stats; + let error; + try { + stats = fs.statSync(ecsDir); + } catch (err) { + error = err; + } + + if (error || !stats.isDirectory()) { + logError( + `directory not found: ${ecsDir} - did you checkout elastic/ecs as a peer of this repo?` + ); + } + + return ecsDir; +} + +function logError(message) { + console.log(`error: ${message}`); + process.exit(1); +} + +const SchemaFileTemplate = ` +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// ---------------------------------- WARNING ---------------------------------- +// this file was generated, and should not be edited by hand +// ---------------------------------- WARNING ---------------------------------- + +// provides TypeScript and config-schema interfaces for ECS for use with +// the event log + +import { schema, TypeOf } from '@kbn/config-schema'; + +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; +type DeepPartial = { + [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; +}; + +export const ECS_VERSION = '%%ECS_VERSION%%'; + +// types and config-schema describing the es structures +export type IValidatedEvent = TypeOf; +export type IEvent = DeepPartial>; + +export const EventSchema = %%SCHEMA%%; + +function ecsStringMulti() { + return schema.maybe(schema.arrayOf(schema.string())); +} + +function ecsString() { + return schema.maybe(schema.string()); +} + +function ecsNumber() { + return schema.maybe(schema.number()); +} + +function ecsDate() { + return schema.maybe(schema.string({ validate: validateDate })); +} + +const ISO_DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/; + +function validateDate(isoDate: string) { + if (ISO_DATE_PATTERN.test(isoDate)) return; + return 'string is not a valid ISO date: ' + isoDate; +} +`.trim(); + +function getSchemaFileContents(ecsVersion, schemaLines) { + return SchemaFileTemplate.replace('%%ECS_VERSION%%', ecsVersion).replace( + '%%SCHEMA%%', + schemaLines + ); + // .replace('%%INTERFACE%%', interfaceLines); +} + +// run as a command-line script +if (require.main === module) main(); diff --git a/x-pack/plugins/event_log/scripts/lib/line_writer.js b/x-pack/plugins/event_log/scripts/lib/line_writer.js new file mode 100644 index 00000000000000..0da39022021196 --- /dev/null +++ b/x-pack/plugins/event_log/scripts/lib/line_writer.js @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const INDENT_LENGTH = 2; +const INDENT = ''.padStart(INDENT_LENGTH); + +module.exports = { + createLineWriter, +}; + +class LineWriter { + constructor() { + this._indent = ''; + this._lines = []; + } + + addLine(line) { + this._lines.push(`${this._indent}${line}`); + } + + indent() { + this._indent = `${this._indent}${INDENT}`; + } + + dedent() { + this._indent = this._indent.substr(INDENT_LENGTH); + } + + getContent() { + return this._lines.join('\n'); + } +} + +function createLineWriter() { + return new LineWriter(); +} diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js new file mode 100644 index 00000000000000..43fd0c78183a10 --- /dev/null +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +exports.EcsKibanaExtensionsMappings = { + properties: { + // kibana server uuid + server_uuid: { + type: 'keyword', + ignore_above: 1024, + }, + // relevant kibana space + namespace: { + type: 'keyword', + ignore_above: 1024, + }, + // array of saved object references, for "linking" via search + saved_objects: { + type: 'nested', + properties: { + // 'kibana' for typical saved object, 'task_manager' for TM, etc + store: { + type: 'keyword', + ignore_above: 1024, + }, + id: { + type: 'keyword', + ignore_above: 1024, + }, + type: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + }, +}; + +// ECS and Kibana ECS extension properties to generate +exports.EcsEventLogProperties = [ + '@timestamp', + 'tags', + 'message', + 'ecs.version', + 'event.action', + 'event.provider', + 'event.start', + 'event.duration', + 'event.end', + 'error.message', + 'user.name', + 'kibana.server_uuid', + 'kibana.namespace', + 'kibana.saved_objects.store', + 'kibana.saved_objects.id', + 'kibana.saved_objects.name', + 'kibana.saved_objects.type', +]; + +// properties that can have multiple values (array vs single value) +exports.EcsEventLogMultiValuedProperties = ['tags']; diff --git a/x-pack/plugins/event_log/server/es/context.mock.ts b/x-pack/plugins/event_log/server/es/context.mock.ts new file mode 100644 index 00000000000000..fb894ce6e77875 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/context.mock.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, ClusterClient } from '../../../../../src/core/server'; +import { EsContext } from './context'; + +import { EsNames } from './names'; + +export type EsClusterClient = Pick; + +export interface EsError { + readonly statusCode: number; + readonly message: string; +} + +interface CreateMockEsContextParams { + logger: Logger; + esNames: EsNames; +} + +export function createMockEsContext(params: CreateMockEsContextParams): EsContext { + return new EsContextMock(params); +} + +class EsContextMock implements EsContext { + public logger: Logger; + public esNames: EsNames; + + constructor(params: CreateMockEsContextParams) { + this.logger = params.logger; + this.esNames = params.esNames; + } + + initialize() {} + + async waitTillReady(): Promise { + return true; + } + + async callEs(operation: string, body?: any): Promise { + return {}; + } +} diff --git a/x-pack/plugins/event_log/server/es/context.ts b/x-pack/plugins/event_log/server/es/context.ts new file mode 100644 index 00000000000000..b93c1892d02064 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/context.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, ClusterClient } from 'src/core/server'; + +import { EsNames, getEsNames } from './names'; +import { initializeEs } from './init'; +import { createReadySignal, ReadySignal } from '../lib/ready_signal'; + +export type EsClusterClient = Pick; + +export interface EsContext { + logger: Logger; + esNames: EsNames; + initialize(): void; + waitTillReady(): Promise; + callEs(operation: string, body?: any): Promise; +} + +export interface EsError { + readonly statusCode: number; + readonly message: string; +} + +export function createEsContext(params: EsContextCtorParams): EsContext { + return new EsContextImpl(params); +} + +export interface EsContextCtorParams { + logger: Logger; + clusterClient: EsClusterClient; + indexNameRoot: string; +} + +class EsContextImpl implements EsContext { + public readonly logger: Logger; + public readonly esNames: EsNames; + private readonly clusterClient: EsClusterClient; + private readonly readySignal: ReadySignal; + private initialized: boolean; + + constructor(params: EsContextCtorParams) { + this.logger = params.logger; + this.esNames = getEsNames(params.indexNameRoot); + this.clusterClient = params.clusterClient; + this.readySignal = createReadySignal(); + this.initialized = false; + } + + initialize() { + // only run the initialization method once + if (this.initialized) return; + this.initialized = true; + + this.logger.debug('initializing EsContext'); + + setImmediate(async () => { + try { + await this._initialize(); + this.logger.debug('readySignal.signal(true)'); + this.readySignal.signal(true); + } catch (err) { + this.logger.debug('readySignal.signal(false)'); + this.readySignal.signal(false); + } + }); + } + + async waitTillReady(): Promise { + return await this.readySignal.wait(); + } + + async callEs(operation: string, body?: any): Promise { + try { + this.debug(`callEs(${operation}) calls:`, body); + const result = await this.clusterClient.callAsInternalUser(operation, body); + this.debug(`callEs(${operation}) result:`, result); + return result; + } catch (err) { + this.debug(`callEs(${operation}) error:`, { + message: err.message, + statusCode: err.statusCode, + }); + throw err; + } + } + + private async _initialize() { + await initializeEs(this); + } + + private debug(message: string, object?: any) { + const objectString = object == null ? '' : JSON.stringify(object); + this.logger.debug(`esContext: ${message} ${objectString}`); + } +} diff --git a/x-pack/plugins/event_log/server/es/documents.test.ts b/x-pack/plugins/event_log/server/es/documents.test.ts new file mode 100644 index 00000000000000..2dec23c61de2f8 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/documents.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getIndexTemplate, getIlmPolicy } from './documents'; +import { getEsNames } from './names'; + +describe('getIlmPolicy()', () => { + test('returns the basic structure of an ilm policy', () => { + expect(getIlmPolicy()).toMatchObject({ + policy: { + phases: {}, + }, + }); + }); +}); + +describe('getIndexTemplate()', () => { + const esNames = getEsNames('XYZ'); + + test('returns the correct details of the index template', () => { + const indexTemplate = getIndexTemplate(esNames, true); + expect(indexTemplate.index_patterns).toEqual([esNames.indexPattern]); + expect(indexTemplate.aliases[esNames.alias]).toEqual({}); + expect(indexTemplate.settings.number_of_shards).toBeGreaterThanOrEqual(0); + expect(indexTemplate.settings.number_of_replicas).toBeGreaterThanOrEqual(0); + expect(indexTemplate.mappings).toMatchObject({}); + }); + + test('returns correct index template bits for ilm when ilm is supported', () => { + const indexTemplate = getIndexTemplate(esNames, true); + expect(indexTemplate.settings['index.lifecycle.name']).toBe(esNames.ilmPolicy); + expect(indexTemplate.settings['index.lifecycle.rollover_alias']).toBe(esNames.alias); + }); + + test('returns correct index template bits for ilm when ilm is not supported', () => { + const indexTemplate = getIndexTemplate(esNames, false); + expect(indexTemplate.settings['index.lifecycle.name']).toBeUndefined(); + expect(indexTemplate.settings['index.lifecycle.rollover_alias']).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts new file mode 100644 index 00000000000000..dfc544f8a41cbf --- /dev/null +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EsNames } from './names'; +import mappings from '../../generated/mappings.json'; + +// returns the body of an index template used in an ES indices.putTemplate call +export function getIndexTemplate(esNames: EsNames, ilmExists: boolean) { + const indexTemplateBody: any = { + index_patterns: [esNames.indexPattern], + aliases: { + [esNames.alias]: {}, + }, + settings: { + number_of_shards: 1, + number_of_replicas: 1, + 'index.lifecycle.name': esNames.ilmPolicy, + 'index.lifecycle.rollover_alias': esNames.alias, + }, + mappings, + }; + + if (!ilmExists) { + delete indexTemplateBody.settings['index.lifecycle.name']; + delete indexTemplateBody.settings['index.lifecycle.rollover_alias']; + } + + return indexTemplateBody; +} + +// returns the body of an ilm policy used in an ES PUT _ilm/policy call +export function getIlmPolicy() { + return { + policy: { + phases: { + hot: { + actions: { + rollover: { + max_size: '5GB', + max_age: '30d', + // max_docs: 1, // you know, for testing + }, + }, + }, + }, + }, + }; +} diff --git a/x-pack/plugins/event_log/server/es/index.ts b/x-pack/plugins/event_log/server/es/index.ts new file mode 100644 index 00000000000000..ad1409e33589fd --- /dev/null +++ b/x-pack/plugins/event_log/server/es/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { EsClusterClient, EsContext, createEsContext } from './context'; diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts new file mode 100644 index 00000000000000..d87f5bce034757 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getIlmPolicy, getIndexTemplate } from './documents'; +import { EsContext } from './context'; + +export async function initializeEs(esContext: EsContext): Promise { + esContext.logger.debug('initializing elasticsearch resources starting'); + + try { + await initializeEsResources(esContext); + } catch (err) { + esContext.logger.error(`error initializing elasticsearch resources: ${err.message}`); + return false; + } + + esContext.logger.debug('initializing elasticsearch resources complete'); + return true; +} + +async function initializeEsResources(esContext: EsContext) { + const steps = new EsInitializationSteps(esContext); + let ilmExists: boolean; + + // create the ilm policy, if required + ilmExists = await steps.doesIlmPolicyExist(); + if (!ilmExists) { + ilmExists = await steps.createIlmPolicy(); + } + + if (!(await steps.doesIndexTemplateExist())) { + await steps.createIndexTemplate({ ilmExists }); + } + + if (!(await steps.doesInitialIndexExist())) { + await steps.createInitialIndex(); + } +} + +interface AddTemplateOpts { + ilmExists: boolean; +} + +class EsInitializationSteps { + constructor(private readonly esContext: EsContext) { + this.esContext = esContext; + } + + async doesIlmPolicyExist(): Promise { + const request = { + method: 'GET', + path: `_ilm/policy/${this.esContext.esNames.ilmPolicy}`, + }; + try { + await this.esContext.callEs('transport.request', request); + } catch (err) { + if (err.statusCode === 404) return false; + // TODO: remove following once kibana user can access ilm + if (err.statusCode === 403) return false; + + throw new Error(`error checking existance of ilm policy: ${err.message}`); + } + return true; + } + + async createIlmPolicy(): Promise { + const request = { + method: 'PUT', + path: `_ilm/policy/${this.esContext.esNames.ilmPolicy}`, + body: getIlmPolicy(), + }; + try { + await this.esContext.callEs('transport.request', request); + } catch (err) { + // TODO: remove following once kibana user can access ilm + if (err.statusCode === 403) return false; + throw new Error(`error creating ilm policy: ${err.message}`); + } + return true; + } + + async doesIndexTemplateExist(): Promise { + const name = this.esContext.esNames.indexTemplate; + let result; + try { + result = await this.esContext.callEs('indices.existsTemplate', { name }); + } catch (err) { + throw new Error(`error checking existance of index template: ${err.message}`); + } + return result as boolean; + } + + async createIndexTemplate(opts: AddTemplateOpts): Promise { + const templateBody = getIndexTemplate(this.esContext.esNames, opts.ilmExists); + const addTemplateParams = { + create: true, + name: this.esContext.esNames.indexTemplate, + body: templateBody, + }; + try { + await this.esContext.callEs('indices.putTemplate', addTemplateParams); + } catch (err) { + throw new Error(`error creating index template: ${err.message}`); + } + } + + async doesInitialIndexExist(): Promise { + const name = this.esContext.esNames.alias; + let result; + try { + result = await this.esContext.callEs('indices.existsAlias', { name }); + } catch (err) { + throw new Error(`error checking existance of initial index: ${err.message}`); + } + return result as boolean; + } + + async createInitialIndex(): Promise { + const index = this.esContext.esNames.initialIndex; + try { + await this.esContext.callEs('indices.create', { index }); + } catch (err) { + throw new Error(`error creating initial index: ${err.message}`); + } + } + + debug(message: string) { + this.esContext.logger.debug(message); + } + + warn(message: string) { + this.esContext.logger.warn(message); + } +} diff --git a/x-pack/plugins/event_log/server/es/names.test.ts b/x-pack/plugins/event_log/server/es/names.test.ts new file mode 100644 index 00000000000000..d88c4212df91ca --- /dev/null +++ b/x-pack/plugins/event_log/server/es/names.test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getEsNames } from './names'; + +describe('getEsNames()', () => { + test('works as expected', () => { + const base = 'XYZ'; + const esNames = getEsNames(base); + expect(esNames.base).toEqual(base); + expect(esNames.alias).toEqual(`${base}-event-log`); + expect(esNames.ilmPolicy).toEqual(`${base}-event-log-policy`); + expect(esNames.indexPattern).toEqual(`${base}-event-log-*`); + expect(esNames.initialIndex).toEqual(`${base}-event-log-000001`); + expect(esNames.indexTemplate).toEqual(`${base}-event-log-template`); + }); +}); diff --git a/x-pack/plugins/event_log/server/es/names.ts b/x-pack/plugins/event_log/server/es/names.ts new file mode 100644 index 00000000000000..be737d23625f14 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/names.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const EVENT_LOG_NAME_SUFFIX = '-event-log'; + +export interface EsNames { + base: string; + alias: string; + ilmPolicy: string; + indexPattern: string; + initialIndex: string; + indexTemplate: string; +} + +export function getEsNames(baseName: string): EsNames { + const eventLogName = `${baseName}${EVENT_LOG_NAME_SUFFIX}`; + return { + base: baseName, + alias: eventLogName, + ilmPolicy: `${eventLogName}-policy`, + indexPattern: `${eventLogName}-*`, + initialIndex: `${eventLogName}-000001`, + indexTemplate: `${eventLogName}-template`, + }; +} diff --git a/x-pack/plugins/event_log/server/event_log_service.test.ts b/x-pack/plugins/event_log/server/event_log_service.test.ts new file mode 100644 index 00000000000000..5ab019e533dae1 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_service.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEventLogConfig } from './types'; +import { EventLogService } from './event_log_service'; +import { getEsNames } from './es/names'; +import { createMockEsContext } from './es/context.mock'; +import { loggingServiceMock } from '../../../../src/core/server/logging/logging_service.mock'; + +const loggingService = loggingServiceMock.create(); +const systemLogger = loggingService.get(); + +describe('EventLogService', () => { + const esContext = createMockEsContext({ + esNames: getEsNames('ABC'), + logger: systemLogger, + }); + + function getService(config: IEventLogConfig) { + const { enabled, logEntries, indexEntries } = config; + return new EventLogService({ + esContext, + systemLogger, + config: { + enabled, + logEntries, + indexEntries, + }, + }); + } + + test('returns config values from service methods', () => { + let service; + + service = getService({ enabled: true, logEntries: true, indexEntries: true }); + expect(service.isEnabled()).toEqual(true); + expect(service.isLoggingEntries()).toEqual(true); + expect(service.isIndexingEntries()).toEqual(true); + + service = getService({ enabled: true, logEntries: false, indexEntries: true }); + expect(service.isEnabled()).toEqual(true); + expect(service.isLoggingEntries()).toEqual(false); + expect(service.isIndexingEntries()).toEqual(true); + + service = getService({ enabled: true, logEntries: true, indexEntries: false }); + expect(service.isEnabled()).toEqual(true); + expect(service.isLoggingEntries()).toEqual(true); + expect(service.isIndexingEntries()).toEqual(false); + + service = getService({ enabled: true, logEntries: false, indexEntries: false }); + expect(service.isEnabled()).toEqual(true); + expect(service.isLoggingEntries()).toEqual(false); + expect(service.isIndexingEntries()).toEqual(false); + + // this is the only non-obvious one; when enabled is false, + // logging/indexing will be false as well. + service = getService({ enabled: false, logEntries: true, indexEntries: true }); + expect(service.isEnabled()).toEqual(false); + expect(service.isLoggingEntries()).toEqual(false); + expect(service.isIndexingEntries()).toEqual(false); + }); + + test('handles registering provider actions correctly', () => { + const params = { + esContext, + systemLogger, + config: { + enabled: true, + logEntries: true, + indexEntries: true, + }, + }; + + const service = new EventLogService(params); + let providerActions: ReturnType; + providerActions = service.getProviderActions(); + expect(providerActions.size).toEqual(0); + + service.registerProviderActions('foo', ['foo-1', 'foo-2']); + providerActions = service.getProviderActions(); + expect(providerActions.size).toEqual(1); + expect(providerActions.get('foo')).toEqual(new Set(['foo-1', 'foo-2'])); + + expect(() => { + service.registerProviderActions('invalid', []); + }).toThrow('actions parameter must not be empty for provider: "invalid"'); + + expect(() => { + service.registerProviderActions('foo', ['abc']); + }).toThrow('provider already registered: "foo"'); + expect(providerActions.get('foo')!.size).toEqual(2); + + expect(service.isProviderActionRegistered('foo', 'foo-1')).toEqual(true); + expect(service.isProviderActionRegistered('foo', 'foo-2')).toEqual(true); + expect(service.isProviderActionRegistered('foo', 'foo-3')).toEqual(false); + expect(service.isProviderActionRegistered('invalid', 'foo')).toEqual(false); + }); + + test('returns a non-null logger from getLogger()', () => { + const params = { + esContext, + systemLogger, + config: { + enabled: true, + logEntries: true, + indexEntries: true, + }, + }; + const service = new EventLogService(params); + const eventLogger = service.getLogger({}); + expect(eventLogger).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts new file mode 100644 index 00000000000000..2746d86b8b2cb9 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { Observable } from 'rxjs'; +import { ClusterClient } from 'src/core/server'; + +import { Plugin } from './plugin'; +import { EsContext } from './es'; +import { IEvent, IEventLogger, IEventLogService, IEventLogConfig } from './types'; +import { EventLogger } from './event_logger'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +type SystemLogger = Plugin['systemLogger']; + +interface EventLogServiceCtorParams { + config: IEventLogConfig; + esContext: EsContext; + systemLogger: SystemLogger; +} + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogService implements IEventLogService { + private config: IEventLogConfig; + private systemLogger: SystemLogger; + private esContext: EsContext; + private registeredProviderActions: Map>; + + constructor({ config, systemLogger, esContext }: EventLogServiceCtorParams) { + this.config = config; + this.esContext = esContext; + this.systemLogger = systemLogger; + this.registeredProviderActions = new Map>(); + } + + public isEnabled(): boolean { + return this.config.enabled; + } + + public isLoggingEntries(): boolean { + return this.isEnabled() && this.config.logEntries; + } + + public isIndexingEntries(): boolean { + return this.isEnabled() && this.config.indexEntries; + } + + registerProviderActions(provider: string, actions: string[]): void { + if (actions.length === 0) { + throw new Error(`actions parameter must not be empty for provider: "${provider}"`); + } + + if (this.registeredProviderActions.has(provider)) { + throw new Error(`provider already registered: "${provider}"`); + } + + this.registeredProviderActions.set(provider, new Set(actions)); + } + + isProviderActionRegistered(provider: string, action: string): boolean { + const actions = this.registeredProviderActions.get(provider); + if (actions == null) return false; + + if (actions.has(action)) return true; + + return false; + } + + getProviderActions() { + return new Map(this.registeredProviderActions.entries()); + } + + getLogger(initialProperties: IEvent): IEventLogger { + return new EventLogger({ + esContext: this.esContext, + eventLogService: this, + initialProperties, + systemLogger: this.systemLogger, + }); + } +} diff --git a/x-pack/plugins/event_log/server/event_logger.mock.ts b/x-pack/plugins/event_log/server/event_logger.mock.ts new file mode 100644 index 00000000000000..97c2b9f980dcd1 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_logger.mock.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEvent, IEventLogger } from './types'; + +export function createEventLoggerMock(): IEventLogger { + return { + logEvent(eventProperties: IEvent): void {}, + startTiming(event: IEvent): void {}, + stopTiming(event: IEvent): void {}, + }; +} diff --git a/x-pack/plugins/event_log/server/event_logger.test.ts b/x-pack/plugins/event_log/server/event_logger.test.ts new file mode 100644 index 00000000000000..0b06515c00bf3c --- /dev/null +++ b/x-pack/plugins/event_log/server/event_logger.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEvent, IEventLogger } from './index'; +import { EventLogService } from './event_log_service'; +import { getEsNames } from './es/names'; +import { createMockEsContext } from './es/context.mock'; +import { loggingServiceMock } from '../../../../src/core/server/logging/logging_service.mock'; +import { delay } from './lib/delay'; + +const loggingService = loggingServiceMock.create(); +const systemLogger = loggingService.get(); + +describe('EventLogger', () => { + const esContext = createMockEsContext({ esNames: getEsNames('ABC'), logger: systemLogger }); + const config = { enabled: true, logEntries: true, indexEntries: true }; + const service = new EventLogService({ esContext, systemLogger, config }); + let eventLogger: IEventLogger; + + beforeEach(() => { + eventLogger = service.getLogger({}); + }); + + test('logEvent()', () => { + service.registerProviderActions('test-provider', ['test-action-1']); + const initialProperties = { + event: { provider: 'test-provider' }, + }; + eventLogger = service.getLogger(initialProperties); + + // ATM, just make sure it doesn't blow up + eventLogger.logEvent({}); + }); + + test('timing', async () => { + const event: IEvent = {}; + eventLogger.startTiming(event); + + const timeStart = event.event!.start!; + expect(timeStart).toBeTruthy(); + expect(new Date(timeStart)).toBeTruthy(); + + await delay(100); + eventLogger.stopTiming(event); + + const timeStop = event.event!.end!; + expect(timeStop).toBeTruthy(); + expect(new Date(timeStop)).toBeTruthy(); + + const duration = event.event!.duration!; + expect(duration).toBeGreaterThan(90 * 1000 * 1000); + }); + + test('timing - no start', async () => { + const event: IEvent = {}; + eventLogger.stopTiming(event); + + expect(event.event).toBeUndefined(); + }); + + test('timing - bad start', async () => { + const event: IEvent = { + event: { + start: 'not a date that can be parsed', + }, + }; + eventLogger.stopTiming(event); + + expect(event.event!.end).toBeUndefined(); + expect(event.event!.duration).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/event_log/server/event_logger.ts b/x-pack/plugins/event_log/server/event_logger.ts new file mode 100644 index 00000000000000..50cf83f55730b2 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_logger.ts @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { Logger } from 'src/core/server'; +import { merge } from 'lodash'; + +import { Plugin } from './plugin'; +import { EsContext } from './es'; +import { + IEvent, + IValidatedEvent, + IEventLogger, + IEventLogService, + ECS_VERSION, + EventSchema, +} from './types'; + +type SystemLogger = Plugin['systemLogger']; + +interface Doc { + index: string; + body: IEvent; +} + +interface IEventLoggerCtorParams { + esContext: EsContext; + eventLogService: IEventLogService; + initialProperties: IEvent; + systemLogger: SystemLogger; +} + +export class EventLogger implements IEventLogger { + private esContext: EsContext; + private eventLogService: IEventLogService; + private initialProperties: IEvent; + private systemLogger: SystemLogger; + + constructor(ctorParams: IEventLoggerCtorParams) { + this.esContext = ctorParams.esContext; + this.eventLogService = ctorParams.eventLogService; + this.initialProperties = ctorParams.initialProperties; + this.systemLogger = ctorParams.systemLogger; + } + + startTiming(event: IEvent): void { + if (event == null) return; + event.event = event.event || {}; + + event.event.start = new Date().toISOString(); + } + + stopTiming(event: IEvent): void { + if (event?.event == null) return; + + const start = getEventStart(event); + if (start == null || isNaN(start)) return; + + const end = Date.now(); + event.event.end = new Date(end).toISOString(); + event.event.duration = (end - start) * 1000 * 1000; // nanoseconds + } + + // non-blocking, but spawns an async task to do the work + logEvent(eventProperties: IEvent): void { + if (!this.eventLogService.isEnabled()) return; + + const event: IEvent = {}; + + // merge the initial properties and event properties + merge(event, this.initialProperties, eventProperties); + + // add fixed properties + event['@timestamp'] = new Date().toISOString(); + event.ecs = event.ecs || {}; + event.ecs.version = ECS_VERSION; + + // TODO add kibana server uuid + // event.kibana.server_uuid = NP version of config.get('server.uuid'); + + let validatedEvent: IValidatedEvent; + try { + validatedEvent = validateEvent(this.eventLogService, event); + } catch (err) { + this.systemLogger.warn(`invalid event logged: ${err.message}`); + return; + } + + const doc: Doc = { + index: this.esContext.esNames.alias, + body: validatedEvent, + }; + + if (this.eventLogService.isIndexingEntries()) { + indexEventDoc(this.esContext, doc); + } + + if (this.eventLogService.isLoggingEntries()) { + logEventDoc(this.systemLogger, doc); + } + } +} + +// return the epoch millis of the start date, or null; may be NaN if garbage +function getEventStart(event: IEvent): number | null { + if (event?.event?.start == null) return null; + + return Date.parse(event.event.start); +} + +const RequiredEventSchema = schema.object({ + provider: schema.string({ minLength: 1 }), + action: schema.string({ minLength: 1 }), +}); + +function validateEvent(eventLogService: IEventLogService, event: IEvent): IValidatedEvent { + if (event?.event == null) { + throw new Error(`no "event" property`); + } + + // ensure there are provider/action properties in event as strings + const requiredProps = { + provider: event.event.provider, + action: event.event.action, + }; + + // will throw an error if structure doesn't validate + const { provider, action } = RequiredEventSchema.validate(requiredProps); + + if (!eventLogService.isProviderActionRegistered(provider, action)) { + throw new Error(`unregistered provider/action: "${provider}" / "${action}"`); + } + + // could throw an error + return EventSchema.validate(event); +} + +function logEventDoc(logger: Logger, doc: Doc): void { + setImmediate(() => { + logger.info(`event logged ${JSON.stringify(doc.body)}`); + }); +} + +function indexEventDoc(esContext: EsContext, doc: Doc): void { + // TODO: + // the setImmediate() on an async function is a little overkill, but, + // setImmediate() may be tweakable via node params, whereas async + // tweaking is in the v8 params realm, which is very dicey. + // Long-term, we should probably create an in-memory queue for this, so + // we can explictly see/set the queue lengths. + + // already verified this.clusterClient isn't null above + setImmediate(async () => { + try { + await indexLogEventDoc(esContext, doc); + } catch (err) { + esContext.logger.warn(`error writing event doc: ${err.message}`); + writeLogEventDocOnError(esContext, doc); + } + }); +} + +// whew, the thing that actually writes the event log document! +async function indexLogEventDoc(esContext: EsContext, doc: any) { + esContext.logger.debug(`writing to event log: ${JSON.stringify(doc)}`); + await esContext.waitTillReady(); + await esContext.callEs('index', doc); + esContext.logger.debug(`writing to event log complete`); +} + +// TODO: write log entry to a bounded queue buffer +function writeLogEventDocOnError(esContext: EsContext, doc: any) { + esContext.logger.warn(`unable to write event doc: ${JSON.stringify(doc)}`); +} diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts new file mode 100644 index 00000000000000..81a56faa49964c --- /dev/null +++ b/x-pack/plugins/event_log/server/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { ConfigSchema } from './types'; +import { Plugin } from './plugin'; + +export { IEventLogService, IEventLogger, IEvent } from './types'; +export const config = { schema: ConfigSchema }; +export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts b/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts new file mode 100644 index 00000000000000..c1bb6d70879f3b --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createBoundedQueue } from './bounded_queue'; +import { loggingServiceMock } from '../../../../../src/core/server/logging/logging_service.mock'; + +const loggingService = loggingServiceMock.create(); +const logger = loggingService.get(); + +describe('basic', () => { + let discardedHelper: DiscardedHelper; + let onDiscarded: (object: number) => void; + let queue2: ReturnType; + let queue10: ReturnType; + + beforeAll(() => { + discardedHelper = new DiscardedHelper(); + onDiscarded = discardedHelper.onDiscarded.bind(discardedHelper); + }); + + beforeEach(() => { + queue2 = createBoundedQueue({ logger, maxLength: 2, onDiscarded }); + queue10 = createBoundedQueue({ logger, maxLength: 10, onDiscarded }); + }); + + test('queued items: 0', () => { + discardedHelper.reset(); + expect(queue2.isEmpty()).toEqual(true); + expect(queue2.isFull()).toEqual(false); + expect(queue2.isCloseToFull()).toEqual(false); + expect(queue2.length).toEqual(0); + expect(queue2.maxLength).toEqual(2); + expect(queue2.pull(1)).toEqual([]); + expect(queue2.pull(100)).toEqual([]); + expect(discardedHelper.discarded).toEqual([]); + }); + + test('queued items: 1', () => { + discardedHelper.reset(); + queue2.push(1); + expect(queue2.isEmpty()).toEqual(false); + expect(queue2.isFull()).toEqual(false); + expect(queue2.isCloseToFull()).toEqual(false); + expect(queue2.length).toEqual(1); + expect(queue2.maxLength).toEqual(2); + expect(queue2.pull(1)).toEqual([1]); + expect(queue2.pull(1)).toEqual([]); + expect(discardedHelper.discarded).toEqual([]); + }); + + test('queued items: 2', () => { + discardedHelper.reset(); + queue2.push(1); + queue2.push(2); + expect(queue2.isEmpty()).toEqual(false); + expect(queue2.isFull()).toEqual(true); + expect(queue2.isCloseToFull()).toEqual(true); + expect(queue2.length).toEqual(2); + expect(queue2.maxLength).toEqual(2); + expect(queue2.pull(1)).toEqual([1]); + expect(queue2.pull(1)).toEqual([2]); + expect(queue2.pull(1)).toEqual([]); + expect(discardedHelper.discarded).toEqual([]); + }); + + test('queued items: 3', () => { + discardedHelper.reset(); + queue2.push(1); + queue2.push(2); + queue2.push(3); + expect(queue2.isEmpty()).toEqual(false); + expect(queue2.isFull()).toEqual(true); + expect(queue2.isCloseToFull()).toEqual(true); + expect(queue2.length).toEqual(2); + expect(queue2.maxLength).toEqual(2); + expect(queue2.pull(1)).toEqual([2]); + expect(queue2.pull(1)).toEqual([3]); + expect(queue2.pull(1)).toEqual([]); + expect(discardedHelper.discarded).toEqual([1]); + }); + + test('closeToFull()', () => { + discardedHelper.reset(); + + expect(queue10.isCloseToFull()).toEqual(false); + + for (let i = 1; i <= 8; i++) { + queue10.push(i); + expect(queue10.isCloseToFull()).toEqual(false); + } + + queue10.push(9); + expect(queue10.isCloseToFull()).toEqual(true); + + queue10.push(10); + expect(queue10.isCloseToFull()).toEqual(true); + + queue10.pull(2); + expect(queue10.isCloseToFull()).toEqual(false); + + queue10.push(11); + expect(queue10.isCloseToFull()).toEqual(true); + }); + + test('discarded', () => { + discardedHelper.reset(); + queue2.push(1); + queue2.push(2); + queue2.push(3); + expect(discardedHelper.discarded).toEqual([1]); + + discardedHelper.reset(); + queue2.push(4); + queue2.push(5); + expect(discardedHelper.discarded).toEqual([2, 3]); + }); + + test('pull', () => { + discardedHelper.reset(); + + expect(queue10.pull(4)).toEqual([]); + + for (let i = 1; i <= 10; i++) { + queue10.push(i); + } + + expect(queue10.pull(4)).toEqual([1, 2, 3, 4]); + expect(queue10.length).toEqual(6); + expect(queue10.pull(4)).toEqual([5, 6, 7, 8]); + expect(queue10.length).toEqual(2); + expect(queue10.pull(4)).toEqual([9, 10]); + expect(queue10.length).toEqual(0); + expect(queue10.pull(1)).toEqual([]); + expect(queue10.pull(4)).toEqual([]); + }); +}); + +class DiscardedHelper { + private _discarded: T[]; + + constructor() { + this.reset(); + this._discarded = []; + this.onDiscarded = this.onDiscarded.bind(this); + } + + onDiscarded(object: T) { + this._discarded.push(object); + } + + public get discarded(): T[] { + return this._discarded; + } + + reset() { + this._discarded = []; + } +} diff --git a/x-pack/plugins/event_log/server/lib/bounded_queue.ts b/x-pack/plugins/event_log/server/lib/bounded_queue.ts new file mode 100644 index 00000000000000..2c5ebcd38f5a8a --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/bounded_queue.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin } from '../plugin'; + +const CLOSE_TO_FULL_PERCENT = 0.9; + +type SystemLogger = Plugin['systemLogger']; + +export interface IBoundedQueue { + maxLength: number; + length: number; + push(object: T): void; + pull(count: number): T[]; + isEmpty(): boolean; + isFull(): boolean; + isCloseToFull(): boolean; +} + +export interface CreateBoundedQueueParams { + maxLength: number; + onDiscarded(object: T): void; + logger: SystemLogger; +} + +export function createBoundedQueue(params: CreateBoundedQueueParams): IBoundedQueue { + if (params.maxLength <= 0) throw new Error(`invalid bounded queue maxLength ${params.maxLength}`); + + return new BoundedQueue(params); +} + +class BoundedQueue implements IBoundedQueue { + private _maxLength: number; + private _buffer: T[]; + private _onDiscarded: (object: T) => void; + private _logger: SystemLogger; + + constructor(params: CreateBoundedQueueParams) { + this._maxLength = params.maxLength; + this._buffer = []; + this._onDiscarded = params.onDiscarded; + this._logger = params.logger; + } + + public get maxLength(): number { + return this._maxLength; + } + + public get length(): number { + return this._buffer.length; + } + + isEmpty() { + return this._buffer.length === 0; + } + + isFull() { + return this._buffer.length >= this._maxLength; + } + + isCloseToFull() { + return this._buffer.length / this._maxLength >= CLOSE_TO_FULL_PERCENT; + } + + push(object: T) { + this.ensureRoom(); + this._buffer.push(object); + } + + pull(count: number) { + if (count <= 0) throw new Error(`invalid pull count ${count}`); + + return this._buffer.splice(0, count); + } + + private ensureRoom() { + if (this.length < this._maxLength) return; + + const discarded = this.pull(this.length - this._maxLength + 1); + for (const object of discarded) { + try { + this._onDiscarded(object!); + } catch (err) { + this._logger.warn(`error discarding circular buffer entry: ${err.message}`); + } + } + } +} diff --git a/x-pack/plugins/event_log/server/lib/delay.test.ts b/x-pack/plugins/event_log/server/lib/delay.test.ts new file mode 100644 index 00000000000000..22355ebec505de --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/delay.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { delay } from './delay'; + +const TEST_DELAY = 100; + +describe('delay', () => { + test('works as expected', async () => { + const timeStart = Date.now(); + await delay(TEST_DELAY); + + // note: testing with .toBeGreaterThanOrEqual(TEST_DELAY) is flaky, + // sometimes the actual value is TEST_DELAY - 1, so ... using that as the + // value to test against; something funky with time rounding I'd guess. + expect(Date.now() - timeStart).toBeGreaterThanOrEqual(TEST_DELAY - 1); + }); +}); diff --git a/x-pack/plugins/event_log/server/lib/delay.ts b/x-pack/plugins/event_log/server/lib/delay.ts new file mode 100644 index 00000000000000..a37569baaf6928 --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/delay.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export async function delay(millis: number) { + await new Promise(resolve => setTimeout(resolve, millis)); +} diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts new file mode 100644 index 00000000000000..d4dbb9064a1ba5 --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createReadySignal, ReadySignal } from './ready_signal'; + +describe('ReadySignal', () => { + let readySignal: ReadySignal; + + beforeEach(() => { + readySignal = createReadySignal(); + }); + + test('works as expected', async done => { + let value = 41; + + timeoutSet(100, () => { + expect(value).toBe(41); + }); + + timeoutSet(250, () => readySignal.signal(42)); + + timeoutSet(400, async () => { + expect(value).toBe(42); + + const innerValue = await readySignal.wait(); + expect(innerValue).toBe(42); + done(); + }); + + value = await readySignal.wait(); + expect(value).toBe(42); + }); +}); + +function timeoutSet(ms: number, fn: any) { + setTimeout(fn, ms); +} diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.ts b/x-pack/plugins/event_log/server/lib/ready_signal.ts new file mode 100644 index 00000000000000..2ea8e655089daa --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/ready_signal.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ReadySignal { + wait(): Promise; + signal(value: T): void; +} + +export function createReadySignal(): ReadySignal { + let resolver: (value: T) => void; + + const promise = new Promise(resolve => { + resolver = resolve; + }); + + async function wait(): Promise { + return await promise; + } + + function signal(value: T) { + resolver(value); + } + + return { wait, signal }; +} diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts new file mode 100644 index 00000000000000..5bc0bcd4287b4b --- /dev/null +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first } from 'rxjs/operators'; +import { + CoreSetup, + CoreStart, + Logger, + Plugin as CorePlugin, + PluginInitializerContext, + ClusterClient, +} from 'src/core/server'; + +import { IEventLogConfig, IEventLogService, IEventLogger, IEventLogConfig$ } from './types'; +import { EventLogService } from './event_log_service'; +import { createEsContext, EsContext } from './es'; + +export type PluginClusterClient = Pick; + +// TODO - figure out how to get ${kibana.index} for `.kibana` +const KIBANA_INDEX = '.kibana'; + +const PROVIDER = 'event_log'; +const ACTIONS = { + starting: 'starting', + stopping: 'stopping', +}; + +export class Plugin implements CorePlugin { + private readonly config$: IEventLogConfig$; + private systemLogger: Logger; + private eventLogService?: IEventLogService; + private esContext?: EsContext; + private eventLogger?: IEventLogger; + + constructor(private readonly context: PluginInitializerContext) { + this.systemLogger = this.context.logger.get(); + this.config$ = this.context.config.create(); + } + + async setup(core: CoreSetup): Promise { + this.systemLogger.debug('setting up plugin'); + + const config = await this.config$.pipe(first()).toPromise(); + + this.esContext = createEsContext({ + logger: this.systemLogger, + // TODO: get index prefix from config.get(kibana.index) + indexNameRoot: KIBANA_INDEX, + clusterClient: core.elasticsearch.adminClient, + }); + + this.eventLogService = new EventLogService({ + config, + esContext: this.esContext, + systemLogger: this.systemLogger, + }); + + this.eventLogService.registerProviderActions(PROVIDER, Object.values(ACTIONS)); + + this.eventLogger = this.eventLogService.getLogger({ + event: { provider: PROVIDER }, + }); + + return this.eventLogService; + } + + async start(core: CoreStart) { + this.systemLogger.debug('starting plugin'); + + if (!this.esContext) throw new Error('esContext not initialized'); + if (!this.eventLogger) throw new Error('eventLogger not initialized'); + if (!this.eventLogService) throw new Error('eventLogService not initialized'); + + // launches initialization async + if (this.eventLogService.isIndexingEntries()) { + this.esContext.initialize(); + } + + // will log the event after initialization + this.eventLogger.logEvent({ + event: { action: ACTIONS.starting }, + message: 'event_log starting', + }); + } + + stop() { + this.systemLogger.debug('stopping plugin'); + + if (!this.eventLogger) throw new Error('eventLogger not initialized'); + + // note that it's unlikely this event would ever be written, + // when Kibana is actuaelly stopping, as it's written asynchronously + this.eventLogger.logEvent({ + event: { action: ACTIONS.stopping }, + message: 'event_log stopping', + }); + } +} diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts new file mode 100644 index 00000000000000..4cace59752f366 --- /dev/null +++ b/x-pack/plugins/event_log/server/types.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { schema, TypeOf } from '@kbn/config-schema'; + +export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; +import { IEvent } from '../generated/schemas'; + +export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + logEntries: schema.boolean({ defaultValue: false }), + indexEntries: schema.boolean({ defaultValue: false }), +}); + +export type IEventLogConfig = TypeOf; +export type IEventLogConfig$ = Observable>; + +// the object exposed by plugin.setup() +export interface IEventLogService { + isEnabled(): boolean; + isLoggingEntries(): boolean; + isIndexingEntries(): boolean; + registerProviderActions(provider: string, actions: string[]): void; + isProviderActionRegistered(provider: string, action: string): boolean; + getProviderActions(): Map>; + + getLogger(properties: IEvent): IEventLogger; +} + +export interface IEventLogger { + logEvent(properties: IEvent): void; + startTiming(event: IEvent): void; + stopTiming(event: IEvent): void; +} diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index f322945e236b8c..d23bd2ac4646d5 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -75,6 +75,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', + '--xpack.event_log.logEntries=true', ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index b5d201c1682bd4..9babed9799b928 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -63,7 +63,7 @@ export default function(kibana: any) { }), }, async executor({ config, secrets, params, services }: ActionTypeExecutorOptions) { - return await services.callCluster('index', { + await services.callCluster('index', { index: params.index, refresh: 'wait_for', body: { @@ -97,7 +97,7 @@ export default function(kibana: any) { source: 'action:test.failing', }, }); - throw new Error('Failed to execute action type'); + throw new Error(`expected failure for ${params.index} ${params.reference}`); }, }; const rateLimitedActionType: ActionType = { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index a0eac43d06c667..c2e5aa041055d8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -88,6 +88,38 @@ export default function({ getService }: FtrProviderContext) { }); }); + it('should handle failed executions', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'failing action', + actionTypeId: 'test.failing', + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); + + const reference = `actions-failure-1:${Spaces.space1.id}`; + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action/${createdAction.id}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + reference, + index: ES_TEST_INDEX_NAME, + }, + }); + + expect(response.statusCode).to.eql(200); + expect(response.body).to.eql({ + actionId: createdAction.id, + status: 'error', + message: 'an error occurred while running the action executor', + serviceMessage: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, + retry: false, + }); + }); + it(`shouldn't execute an action from another space`, async () => { const { body: createdAction } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) From 04374e665ca557abca33b4f96daf5df11c9b936c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 21 Jan 2020 18:52:13 -0500 Subject: [PATCH 40/59] [Maps] show field type icons in data driven styling field select (#55166) * [Maps] show field icons in data driven styling field select * only show origin group label when there is more then one origin * review feedback Co-authored-by: Elastic Machine --- .../components/color/color_map_select.js | 60 +++--- .../components/color/dynamic_color_form.js | 188 +++++++----------- .../styles/vector/components/field_select.js | 99 +++++---- .../vector/components/vector_style_editor.js | 1 + 4 files changed, 163 insertions(+), 185 deletions(-) diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index 242b71522f9a2b..fde088ab4475e6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -14,9 +14,7 @@ import { ColorStopsCategorical } from './color_stops_categorical'; const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP'; export class ColorMapSelect extends Component { - state = { - selected: '', - }; + state = {}; static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.customColorMap === prevState.prevPropsCustomColorMap) { @@ -41,10 +39,7 @@ export class ColorMapSelect extends Component { _onCustomColorMapChange = ({ colorStops, isInvalid }) => { // Manage invalid custom color map in local state if (isInvalid) { - const newState = { - customColorMap: colorStops, - }; - this.setState(newState); + this.setState({ customColorMap: colorStops }); return; } @@ -56,35 +51,34 @@ export class ColorMapSelect extends Component { }; _renderColorStopsInput() { - let colorStopsInput; - if (this.props.useCustomColorMap) { - if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) { - colorStopsInput = ( - - - - - ); - } else if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { - colorStopsInput = ( - - - - - ); - } + if (!this.props.useCustomColorMap) { + return null; } - return colorStopsInput; + + if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) { + return ( + + + + + ); + } + + return ( + + + + + ); } render() { - const colorStopsInput = this._renderColorStopsInput(); const colorMapOptionsWithCustom = [ { value: CUSTOM_COLOR_MAP, @@ -110,7 +104,7 @@ export class ColorMapSelect extends Component { valueOfSelected={valueOfSelected} hasDividers={true} /> - {colorStopsInput} + {this._renderColorStopsInput()} ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index 7994f84386a8a2..ba5621b8efadfd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -13,87 +13,56 @@ import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common import { COLOR_GRADIENTS, COLOR_PALETTES } from '../../../color_utils'; import { i18n } from '@kbn/i18n'; -export class DynamicColorForm extends React.Component { - state = { - colorMapType: COLOR_MAP_TYPE.ORDINAL, - }; - - constructor() { - super(); - this._isMounted = false; - } - - componentWillUnmount() { - this._isMounted = false; - } +export function DynamicColorForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); - componentDidMount() { - this._isMounted = true; - this._loadColorMapType(); - } - - componentDidUpdate() { - this._loadColorMapType(); - } - - async _loadColorMapType() { - const field = this.props.styleProperty.getField(); - if (!field) { - return; - } - const dataType = await field.getDataType(); - const colorMapType = CATEGORICAL_DATA_TYPES.includes(dataType) - ? COLOR_MAP_TYPE.CATEGORICAL - : COLOR_MAP_TYPE.ORDINAL; - if (this._isMounted && this.state.colorMapType !== colorMapType) { - this.setState({ colorMapType }, () => { - const options = this.props.styleProperty.getOptions(); - this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), { - ...options, - type: colorMapType, - }); - }); + const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => { + const newColorOptions = { + ...styleOptions, + type, + }; + if (type === COLOR_MAP_TYPE.ORDINAL) { + newColorOptions.useCustomColorRamp = useCustomColorMap; + newColorOptions.customColorRamp = customColorMap; + newColorOptions.color = color; + } else { + newColorOptions.useCustomColorPalette = useCustomColorMap; + newColorOptions.customColorPalette = customColorMap; + newColorOptions.colorCategory = color; } - } - _getColorSelector() { - const { onDynamicStyleChange, styleProperty } = this.props; - const styleOptions = styleProperty.getOptions(); + onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions); + }; + const onFieldChange = async ({ field }) => { + const { name, origin, type } = field; + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name, origin }, + type: CATEGORICAL_DATA_TYPES.includes(type) + ? COLOR_MAP_TYPE.CATEGORICAL + : COLOR_MAP_TYPE.ORDINAL, + }); + }; + + const renderColorMapSelect = () => { if (!styleOptions.field || !styleOptions.field.name) { - return; + return null; } - let colorSelect; - const onColorChange = colorOptions => { - const newColorOptions = { - type: colorOptions.type, - }; - if (colorOptions.type === COLOR_MAP_TYPE.ORDINAL) { - newColorOptions.useCustomColorRamp = colorOptions.useCustomColorMap; - newColorOptions.customColorRamp = colorOptions.customColorMap; - newColorOptions.color = colorOptions.color; - } else { - newColorOptions.useCustomColorPalette = colorOptions.useCustomColorMap; - newColorOptions.customColorPalette = colorOptions.customColorMap; - newColorOptions.colorCategory = colorOptions.color; - } - - onDynamicStyleChange(styleProperty.getStyleName(), { - ...styleOptions, - ...newColorOptions, - }); - }; - - if (this.state.colorMapType === COLOR_MAP_TYPE.ORDINAL) { - const customOptionLabel = i18n.translate('xpack.maps.style.customColorRampLabel', { - defaultMessage: 'Custom color ramp', - }); - colorSelect = ( + if (styleOptions.type === COLOR_MAP_TYPE.ORDINAL) { + return ( onColorChange(options)} + customOptionLabel={i18n.translate('xpack.maps.style.customColorRampLabel', { + defaultMessage: 'Custom color ramp', + })} + onChange={onColorMapSelect} colorMapType={COLOR_MAP_TYPE.ORDINAL} color={styleOptions.color} customColorMap={styleOptions.customColorRamp} @@ -101,52 +70,39 @@ export class DynamicColorForm extends React.Component { compressed /> ); - } else if (this.state.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { - const customOptionLabel = i18n.translate('xpack.maps.style.customColorPaletteLabel', { - defaultMessage: 'Custom color palette', - }); - colorSelect = ( - onColorChange(options)} - colorMapType={COLOR_MAP_TYPE.CATEGORICAL} - color={styleOptions.colorCategory} - customColorMap={styleOptions.customColorPalette} - useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)} - compressed - /> - ); } - return colorSelect; - } - - render() { - const { fields, onDynamicStyleChange, staticDynamicSelect, styleProperty } = this.props; - const styleOptions = styleProperty.getOptions(); - const onFieldChange = options => { - const field = options.field; - onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); - }; - - const colorSelect = this._getColorSelector(); return ( - - - {staticDynamicSelect} - - - - - - {colorSelect} - + ); - } + }; + + return ( + + + {staticDynamicSelect} + + + + + + {renderColorMapSelect()} + + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js index 1d8f4e13fdd1a5..a32c2ce04d7355 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -7,48 +7,73 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox } from '@elastic/eui'; -import { SOURCE_DATA_ID_ORIGIN } from '../../../../../common/constants'; +import { EuiComboBox, EuiHighlight } from '@elastic/eui'; +import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; +import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; -export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { - const onFieldChange = selectedFields => { - onChange({ - field: selectedFields.length > 0 ? selectedFields[0].value : null, - }); - }; +function renderOption(option, searchValue, contentClassName) { + return ( + + +   + {option.label} + + ); +} - const groupFieldsByOrigin = () => { - const fieldsByOriginMap = new Map(); - fields.forEach(field => { - if (fieldsByOriginMap.has(field.origin)) { - const fieldsList = fieldsByOriginMap.get(field.origin); - fieldsList.push(field); - fieldsByOriginMap.set(field.origin, fieldsList); - } else { - fieldsByOriginMap.set(field.origin, [field]); - } - }); +function groupFieldsByOrigin(fields) { + const fieldsByOriginMap = new Map(); + fields.forEach(field => { + if (fieldsByOriginMap.has(field.origin)) { + const fieldsList = fieldsByOriginMap.get(field.origin); + fieldsList.push(field); + fieldsByOriginMap.set(field.origin, fieldsList); + } else { + fieldsByOriginMap.set(field.origin, [field]); + } + }); - const optionGroups = []; - fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => { - optionGroups.push({ - label: fieldOrigin, - options: fieldsList - .map(field => { - return { value: field, label: field.label }; - }) - .sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); - }), + function fieldsListToOptions(fieldsList) { + return fieldsList + .map(field => { + return { value: field, label: field.label }; + }) + .sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); }); - }); + } + + if (fieldsByOriginMap.size === 1) { + // do not show origin group if all fields are from same origin + const onlyOriginKey = fieldsByOriginMap.keys().next().value; + const fieldsList = fieldsByOriginMap.get(onlyOriginKey); + return fieldsListToOptions(fieldsList); + } - optionGroups.sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + const optionGroups = []; + fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => { + optionGroups.push({ + label: i18n.translate('xpack.maps.style.fieldSelect.OriginLabel', { + defaultMessage: 'Fields from {fieldOrigin}', + values: { fieldOrigin }, + }), + options: fieldsListToOptions(fieldsList), }); + }); + + optionGroups.sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + }); + + return optionGroups; +} - return optionGroups; +export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { + const onFieldChange = selectedFields => { + onChange({ + field: selectedFields.length > 0 ? selectedFields[0].value : null, + }); }; let selectedOption; @@ -61,7 +86,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { return ( ); @@ -76,7 +102,8 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { export const fieldShape = PropTypes.shape({ name: PropTypes.string.isRequired, - origin: PropTypes.oneOf(['join', SOURCE_DATA_ID_ORIGIN]).isRequired, + origin: PropTypes.oneOf(Object.values(FIELD_ORIGIN)).isRequired, + type: PropTypes.string.isRequired, }); FieldSelect.propTypes = { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 2f3cfa1d8e4bb6..0784e2a8cc355c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -61,6 +61,7 @@ export class VectorStyleEditor extends Component { label: await field.getLabel(), name: field.getName(), origin: field.getOrigin(), + type: await field.getDataType(), }; }; From c531eb477bdb17d57bafda0377d6f4653283a361 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 21 Jan 2020 17:39:03 -0700 Subject: [PATCH 41/59] [Reporting] Convert CSV Export libs to Typescript (#55117) * [Reporting] Convert CSV Export libs to Typescript * fix jest tests * more ts conversion Co-authored-by: Elastic Machine --- .../common/cancellation_token.test.ts | 9 --- .../export_types/csv/server/execute_job.ts | 11 ++- .../lib/check_cells_for_formulas.test.ts | 1 + .../csv/server/lib/escape_value.js | 17 ----- .../escape_value.js => escape_value.test.ts} | 6 +- .../csv/server/lib/escape_value.ts | 22 ++++++ ...format_map.js => field_format_map.test.ts} | 12 +-- ...ield_format_map.js => field_format_map.ts} | 21 +++++- .../flatten_hit.js => flatten_hit.test.ts} | 10 ++- .../lib/{flatten_hit.js => flatten_hit.ts} | 22 ++++-- ...sv_values.js => format_csv_values.test.ts} | 6 +- ...mat_csv_values.js => format_csv_values.ts} | 12 ++- .../lib/{generate_csv.js => generate_csv.ts} | 11 ++- .../hit_iterator.ts => hit_iterator.test.ts} | 6 +- .../csv/server/lib/hit_iterator.ts | 2 +- ...der.js => max_size_string_builder.test.ts} | 2 +- ..._builder.js => max_size_string_builder.ts} | 8 +- .../export_types/csv/server/lib/types.d.ts | 7 ++ .../reporting/export_types/csv/types.d.ts | 73 +++++++++++++++++++ .../server/execute_job.ts | 9 +-- .../server/lib/generate_csv_search.ts | 10 ++- .../csv_from_savedobject/types.d.ts | 70 ------------------ .../reporting/server/lib/create_queue.ts | 1 - ...gged_logger.js => create_tagged_logger.ts} | 6 +- .../lib/{jobs_query.js => jobs_query.ts} | 53 +++++++++++--- .../plugins/reporting/server/routes/jobs.ts | 45 ++++++------ ...ting.js => authorized_user_pre_routing.ts} | 26 +++++-- ...ng.js => reporting_feature_pre_routing.ts} | 12 ++- .../routes/lib/route_config_factories.ts | 18 ++--- x-pack/legacy/plugins/reporting/types.d.ts | 6 ++ 30 files changed, 308 insertions(+), 206 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/escape_value.js => escape_value.test.ts} (91%) create mode 100644 x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/field_format_map.js => field_format_map.test.ts} (83%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{field_format_map.js => field_format_map.ts} (71%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/flatten_hit.js => flatten_hit.test.ts} (95%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{flatten_hit.js => flatten_hit.ts} (66%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/format_csv_values.js => format_csv_values.test.ts} (92%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{format_csv_values.js => format_csv_values.ts} (72%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{generate_csv.js => generate_csv.ts} (89%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/hit_iterator.ts => hit_iterator.test.ts} (95%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{__tests__/max_size_string_builder.js => max_size_string_builder.test.ts} (97%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/lib/{max_size_string_builder.js => max_size_string_builder.ts} (83%) create mode 100644 x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts rename x-pack/legacy/plugins/reporting/server/lib/{create_tagged_logger.js => create_tagged_logger.ts} (79%) rename x-pack/legacy/plugins/reporting/server/lib/{jobs_query.js => jobs_query.ts} (70%) rename x-pack/legacy/plugins/reporting/server/routes/lib/{authorized_user_pre_routing.js => authorized_user_pre_routing.ts} (60%) rename x-pack/legacy/plugins/reporting/server/routes/lib/{reporting_feature_pre_routing.js => reporting_feature_pre_routing.ts} (76%) diff --git a/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts b/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts index eac9b6a1a7f35f..5f820f460753ed 100644 --- a/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts +++ b/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts @@ -46,13 +46,4 @@ describe('Cancellation Token', () => { expect(onCancelled).toBeCalled(); }); - - it('throws an error when the callback is not a function', () => { - const cancellationToken = new CancellationToken(); - - expect(() => { - // @ts-ignore - cancellationToken.on('cool!'); - }).toThrowError('Expected callback to be a function'); - }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index ae603d93245a32..f35ffa0e45bfe1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -5,13 +5,16 @@ */ import { i18n } from '@kbn/i18n'; -import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../types'; +import { + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + FieldFormats, + ServerFacade, +} from '../../../types'; import { CSV_JOB_TYPE, PLUGIN_ID } from '../../../common/constants'; import { cryptoFactory, LevelLogger } from '../../../server/lib'; import { JobDocPayloadDiscoverCsv } from '../types'; -// @ts-ignore untyped module TODO import { createGenerateCsv } from './lib/generate_csv'; -// @ts-ignore untyped module TODO import { fieldFormatMapFactory } from './lib/field_format_map'; export const executeJobFactory: ExecuteJobFactory { - const fieldFormats = await server.fieldFormatServiceFactory(uiConfig); + const fieldFormats = (await server.fieldFormatServiceFactory(uiConfig)) as FieldFormats; return fieldFormatMapFactory(indexPatternSavedObject, fieldFormats); })(), (async () => { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts index 31f5a91e5a57b4..972ca1777bd731 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { checkIfRowsHaveFormulas } from './check_cells_for_formulas'; const formulaValues = ['=', '+', '-', '@']; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js deleted file mode 100644 index 4031191c2f8123..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const nonAlphaNumRE = /[^a-zA-Z0-9]/; -const allDoubleQuoteRE = /"/g; - -export function createEscapeValue(quoteValues) { - return function escapeValue(val) { - if (quoteValues && nonAlphaNumRE.test(val)) { - return `"${val.replace(allDoubleQuoteRE, '""')}"`; - } - return val; - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts similarity index 91% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts index 02dc694e1555bc..64b021a2aeea84 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts @@ -5,11 +5,11 @@ */ import expect from '@kbn/expect'; -import { createEscapeValue } from '../escape_value'; +import { createEscapeValue } from './escape_value'; describe('escapeValue', function() { describe('quoteValues is true', function() { - let escapeValue; + let escapeValue: (val: string) => string; beforeEach(function() { escapeValue = createEscapeValue(true); }); @@ -44,7 +44,7 @@ describe('escapeValue', function() { }); describe('quoteValues is false', function() { - let escapeValue; + let escapeValue: (val: string) => string; beforeEach(function() { escapeValue = createEscapeValue(false); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts new file mode 100644 index 00000000000000..563de563350e9e --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RawValue } from './types'; + +const nonAlphaNumRE = /[^a-zA-Z0-9]/; +const allDoubleQuoteRE = /"/g; + +export function createEscapeValue(quoteValues: boolean): (val: RawValue) => string { + return function escapeValue(val: RawValue) { + if (val && typeof val === 'string') { + if (quoteValues && nonAlphaNumRE.test(val)) { + return `"${val.replace(allDoubleQuoteRE, '""')}"`; + } + } + + return val == null ? '' : val.toString(); + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts index 5f6bc32f10d8be..b3384dd6ca51d1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts @@ -5,13 +5,13 @@ */ import expect from '@kbn/expect'; - -import { FieldFormatsService } from '../../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; +import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; // Reporting uses an unconventional directory structure so the linter marks this as a violation // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BytesFormat, NumberFormat } from '../../../../../../../../../src/plugins/data/server'; +import { BytesFormat, NumberFormat } from '../../../../../../../../src/plugins/data/server'; +import { fieldFormatMapFactory } from './field_format_map'; -import { fieldFormatMapFactory } from '../field_format_map'; +type ConfigValue = { number: { id: string; params: {} } } | string; describe('field format map', function() { const indexPatternSavedObject = { @@ -26,12 +26,12 @@ describe('field format map', function() { fieldFormatMap: '{"field1":{"id":"bytes","params":{"pattern":"0,0.[0]b"}}}', }, }; - const configMock = {}; + const configMock: Record = {}; configMock['format:defaultTypeMap'] = { number: { id: 'number', params: {} }, }; configMock['format:number:defaultPattern'] = '0,0.[000]'; - const getConfig = key => configMock[key]; + const getConfig = (key: string) => configMock[key]; const testValue = '4000'; const fieldFormats = new FieldFormatsService([BytesFormat, NumberFormat], getConfig); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts similarity index 71% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts index 17b7361ca07b0c..d013b5e75ee4df 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts @@ -5,6 +5,16 @@ */ import _ from 'lodash'; +import { FieldFormats } from '../../../../types'; + +interface IndexPatternSavedObject { + attributes: { + fieldFormatMap: string; + }; + id: string; + type: string; + version: string; +} /** * Create a map of FieldFormat instances for index pattern fields @@ -13,10 +23,13 @@ import _ from 'lodash'; * @param {FieldFormatsService} fieldFormats * @return {Map} key: field name, value: FieldFormat instance */ -export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { +export function fieldFormatMapFactory( + indexPatternSavedObject: IndexPatternSavedObject, + fieldFormats: FieldFormats +) { const formatsMap = new Map(); - //Add FieldFormat instances for fields with custom formatters + // Add FieldFormat instances for fields with custom formatters if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); Object.keys(fieldFormatMap).forEach(fieldName => { @@ -28,9 +41,9 @@ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { }); } - //Add default FieldFormat instances for all other fields + // Add default FieldFormat instances for all other fields const indexFields = JSON.parse(_.get(indexPatternSavedObject, 'attributes.fields', '[]')); - indexFields.forEach(field => { + indexFields.forEach((field: any) => { if (!formatsMap.has(field.name)) { formatsMap.set(field.name, fieldFormats.getDefaultInstance(field.type)); } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts index 2169c7cab12362..1e06e78357399c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts @@ -5,12 +5,14 @@ */ import expect from '@kbn/expect'; -import { createFlattenHit } from '../flatten_hit'; +import { createFlattenHit } from './flatten_hit'; + +type Hit = Record; describe('flattenHit', function() { - let flattenHit; - let hit; - let metaFields; + let flattenHit: (hit: Hit) => Record; + let hit: Hit; + let metaFields: string[]; beforeEach(function() { const fields = [ diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts similarity index 66% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts index b1387a3ef310c5..328d49a27911c7 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts @@ -6,9 +6,17 @@ import _ from 'lodash'; +type Hit = Record; +type FlattenHitFn = (hit: Hit) => Record; +type FlatHits = Record; + // TODO this logic should be re-used with Discover -export function createFlattenHit(fields, metaFields, conflictedTypesFields) { - const flattenSource = (flat, obj, keyPrefix) => { +export function createFlattenHit( + fields: string[], + metaFields: string[], + conflictedTypesFields: string[] +): FlattenHitFn { + const flattenSource = (flat: FlatHits, obj: object, keyPrefix = '') => { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; _.forOwn(obj, (val, key) => { key = keyPrefix + key; @@ -31,17 +39,19 @@ export function createFlattenHit(fields, metaFields, conflictedTypesFields) { }); }; - const flattenMetaFields = (flat, hit) => { + const flattenMetaFields = (flat: Hit, hit: Hit) => { _.each(metaFields, meta => { if (meta === '_source') return; flat[meta] = hit[meta]; }); }; - const flattenFields = (flat, hitFields) => { + const flattenFields = (flat: FlatHits, hitFields: string[]) => { _.forOwn(hitFields, (val, key) => { - if (key[0] === '_' && !_.contains(metaFields, key)) return; - flat[key] = _.isArray(val) && val.length === 1 ? val[0] : val; + if (key) { + if (key[0] === '_' && !_.contains(metaFields, key)) return; + flat[key] = _.isArray(val) && val.length === 1 ? val[0] : val; + } }); }; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts similarity index 92% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts index 7401109ae80301..b38bad11794717 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; -import { createFormatCsvValues } from '../format_csv_values'; +import { createFormatCsvValues } from './format_csv_values'; describe('formatCsvValues', function() { const separator = ','; const fields = ['foo', 'bar']; - const mockEscapeValue = val => val; + const mockEscapeValue = (value: any, index: number, array: any[]) => value || ''; describe('with _source as one of the fields', function() { const formatsMap = new Map(); const formatCsvValues = createFormatCsvValues( @@ -62,7 +62,7 @@ describe('formatCsvValues', function() { describe('with field formats', function() { const mockFieldFormat = { - convert: val => String(val).toUpperCase(), + convert: (val: string) => String(val).toUpperCase(), }; const formatsMap = new Map(); formatsMap.set('bar', mockFieldFormat); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts similarity index 72% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts index 9083e8ce04f88c..0bcf0fc31beae5 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts @@ -5,9 +5,15 @@ */ import { isObject, isNull, isUndefined } from 'lodash'; +import { RawValue } from './types'; -export function createFormatCsvValues(escapeValue, separator, fields, formatsMap) { - return function formatCsvValues(values) { +export function createFormatCsvValues( + escapeValue: (value: RawValue, index: number, array: RawValue[]) => string, + separator: string, + fields: string[], + formatsMap: any +) { + return function formatCsvValues(values: Record) { return fields .map(field => { let value; @@ -29,7 +35,7 @@ export function createFormatCsvValues(escapeValue, separator, fields, formatsMap return formattedValue; }) .map(value => (isObject(value) ? JSON.stringify(value) : value)) - .map(value => value.toString()) + .map(value => (value ? value.toString() : value)) .map(escapeValue) .join(separator); }; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts index c13d24022c40c4..1986e68917ba86 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Logger } from '../../../../types'; +import { GenerateCsvParams, SavedSearchGeneratorResult } from '../../types'; import { createFlattenHit } from './flatten_hit'; import { createFormatCsvValues } from './format_csv_values'; import { createEscapeValue } from './escape_value'; @@ -11,7 +13,7 @@ import { createHitIterator } from './hit_iterator'; import { MaxSizeStringBuilder } from './max_size_string_builder'; import { checkIfRowsHaveFormulas } from './check_cells_for_formulas'; -export function createGenerateCsv(logger) { +export function createGenerateCsv(logger: Logger) { const hitIterator = createHitIterator(logger); return async function generateCsv({ @@ -23,12 +25,13 @@ export function createGenerateCsv(logger) { callEndpoint, cancellationToken, settings, - }) { + }: GenerateCsvParams): Promise { const escapeValue = createEscapeValue(settings.quoteValues); const builder = new MaxSizeStringBuilder(settings.maxSizeBytes); const header = `${fields.map(escapeValue).join(settings.separator)}\n`; if (!builder.tryAppend(header)) { return { + size: 0, content: '', maxSizeReached: true, }; @@ -49,6 +52,10 @@ export function createGenerateCsv(logger) { while (true) { const { done, value: hit } = await iterator.next(); + if (!hit) { + break; + } + if (done) { break; } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts index 20e373a4168af9..3765217de92859 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts @@ -6,9 +6,9 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { CancellationToken } from '../../../../../common/cancellation_token'; -import { Logger, ScrollConfig } from '../../../../../types'; -import { createHitIterator } from '../hit_iterator'; +import { CancellationToken } from '../../../../common/cancellation_token'; +import { Logger, ScrollConfig } from '../../../../types'; +import { createHitIterator } from './hit_iterator'; const mockLogger = { error: new Function(), diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts index 68836c01369e3f..90690b62ff4a41 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams, SearchResponse } from 'elasticsearch'; +import { SearchParams, SearchResponse } from 'elasticsearch'; import { i18n } from '@kbn/i18n'; import { CancellationToken, ScrollConfig, Logger } from '../../../../types'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts index ddc4299ebcb455..843ff82e7c4bc9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { MaxSizeStringBuilder } from '../max_size_string_builder'; +import { MaxSizeStringBuilder } from './max_size_string_builder'; describe('MaxSizeStringBuilder', function() { describe('tryAppend', function() { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts index b4bdfcc13b3f6e..70bc2030d290c6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts @@ -5,13 +5,17 @@ */ export class MaxSizeStringBuilder { - constructor(maxSizeBytes) { + private _buffer: Buffer; + private _size: number; + private _maxSize: number; + + constructor(maxSizeBytes: number) { this._buffer = Buffer.alloc(maxSizeBytes); this._size = 0; this._maxSize = maxSizeBytes; } - tryAppend(str) { + tryAppend(str: string) { const byteLength = Buffer.byteLength(str); if (this._size + byteLength <= this._maxSize) { this._buffer.write(str, this._size); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts new file mode 100644 index 00000000000000..b4dc7436649956 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type RawValue = string | object | null | undefined; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts index 9d1eb9cc1bd4d3..842330fa7c93f3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CancellationToken } from '../../common/cancellation_token'; import { JobDocPayload, JobParamPostPayload, ConditionalHeaders, RequestFacade } from '../../types'; interface DocValueField { @@ -37,3 +38,75 @@ export interface JobDocPayloadDiscoverCsv extends JobDocPayload; + stored_fields: string[]; + } + | any; +} + +type EndpointCaller = (method: string, params: any) => Promise; + +type FormatsMap = Map< + string, + { + id: string; + params: { + pattern: string; + }; + } +>; + +export interface SavedSearchGeneratorResult { + content: string; + size: number; + maxSizeReached: boolean; + csvContainsFormulas?: boolean; +} + +export interface CsvResultFromSearch { + type: string; + result: SavedSearchGeneratorResult; +} + +export interface GenerateCsvParams { + searchRequest: SearchRequest; + callEndpoint: EndpointCaller; + fields: string[]; + formatsMap: FormatsMap; + metaFields: string[]; + conflictedTypesFields: string[]; + cancellationToken: CancellationToken; + settings: { + separator: string; + quoteValues: boolean; + timezone: string | null; + maxSizeBytes: number; + scroll: { duration: string; size: number }; + checkForFormulas?: boolean; + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index e161d7afc84e27..69d9a173d40b38 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -18,13 +18,8 @@ import { CSV_FROM_SAVEDOBJECT_JOB_TYPE, PLUGIN_ID, } from '../../../common/constants'; -import { - CsvResultFromSearch, - JobParamsPanelCsv, - SearchPanel, - JobDocPayloadPanelCsv, - FakeRequest, -} from '../types'; +import { CsvResultFromSearch } from '../../csv/types'; +import { JobParamsPanelCsv, SearchPanel, JobDocPayloadPanelCsv, FakeRequest } from '../types'; import { createGenerateCsv } from './lib'; export const executeJobFactory: ExecuteJobFactory; - stored_fields: string[]; - } - | any; -} - -export interface SavedSearchGeneratorResult { - content: string; - maxSizeReached: boolean; - size: number; -} - -export interface CsvResultFromSearch { - type: string; - result: SavedSearchGeneratorResult; -} - -type EndpointCaller = (method: string, params: any) => Promise; -type FormatsMap = Map< - string, - { - id: string; - params: { - pattern: string; - }; - } ->; - -export interface GenerateCsvParams { - searchRequest: SearchRequest; - callEndpoint: EndpointCaller; - fields: string[]; - formatsMap: FormatsMap; - metaFields: string[]; // FIXME not sure what this is for - conflictedTypesFields: string[]; // FIXME not sure what this is for - cancellationToken: CancellationToken; - settings: { - separator: string; - quoteValues: boolean; - timezone: string | null; - maxSizeBytes: number; - scroll: { duration: string; size: number }; - }; -} - /* * These filter types are stub types to help ensure things get passed to * non-Typescript functions in the right order. An actual structure is not diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 5cf760250ec0e3..4a9b0c7cd2ebb3 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -15,7 +15,6 @@ import { import { Esqueue } from './esqueue'; import { createWorkerFactory } from './create_worker'; import { LevelLogger } from './level_logger'; -// @ts-ignore import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed interface CreateQueueFactoryOpts { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js b/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts similarity index 79% rename from x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js rename to x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts index 838024f801b676..40a1cd8203d2fe 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js +++ b/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ServerFacade } from '../../types'; + /** * @function taggedLogger * @param {string} message @@ -17,8 +19,8 @@ * @param {string[]} tags - tags to always be passed into the `logger` function * @returns taggedLogger */ -export function createTaggedLogger(server, tags) { - return (msg, additionalTags = []) => { +export function createTaggedLogger(server: ServerFacade, tags: string[]) { + return (msg: string, additionalTags = []) => { server.log([...tags, ...additionalTags], msg); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.js b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts similarity index 70% rename from x-pack/legacy/plugins/reporting/server/lib/jobs_query.js rename to x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts index 2162ecedb8371f..0c16f780c34acd 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.js +++ b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts @@ -5,21 +5,48 @@ */ import { get } from 'lodash'; +import { ServerFacade, JobSource } from '../../types'; const defaultSize = 10; -export function jobsQueryFactory(server) { +interface QueryBody { + size?: number; + from?: number; + _source?: { + excludes: string[]; + }; + query: { + constant_score: { + filter: { + bool: { + must: Array>; + }; + }; + }; + }; +} + +interface GetOpts { + includeContent?: boolean; +} + +interface CountAggResult { + count: number; +} + +export function jobsQueryFactory(server: ServerFacade) { const index = server.config().get('xpack.reporting.index'); + // @ts-ignore `errors` does not exist on type Cluster const { callWithInternalUser, errors: esErrors } = server.plugins.elasticsearch.getCluster( 'admin' ); - function getUsername(user) { + function getUsername(user: any) { return get(user, 'username', false); } - function execQuery(queryType, body) { - const defaultBody = { + function execQuery(queryType: string, body: QueryBody) { + const defaultBody: Record = { search: { _source: { excludes: ['output.content'], @@ -42,15 +69,17 @@ export function jobsQueryFactory(server) { }); } - function getHits(query) { + type Result = number; + + function getHits(query: Promise) { return query.then(res => get(res, 'hits.hits', [])); } return { - list(jobTypes, user, page = 0, size = defaultSize, jobIds) { + list(jobTypes: string[], user: any, page = 0, size = defaultSize, jobIds: string[] | null) { const username = getUsername(user); - const body = { + const body: QueryBody = { size, from: size * page, query: { @@ -73,10 +102,10 @@ export function jobsQueryFactory(server) { return getHits(execQuery('search', body)); }, - count(jobTypes, user) { + count(jobTypes: string[], user: any) { const username = getUsername(user); - const body = { + const body: QueryBody = { query: { constant_score: { filter: { @@ -88,18 +117,18 @@ export function jobsQueryFactory(server) { }, }; - return execQuery('count', body).then(doc => { + return execQuery('count', body).then((doc: CountAggResult) => { if (!doc) return 0; return doc.count; }); }, - get(user, id, opts = {}) { + get(user: any, id: string, opts: GetOpts = {}): Promise | void> { if (!id) return Promise.resolve(); const username = getUsername(user); - const body = { + const body: QueryBody = { query: { constant_score: { filter: { diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index a0be15d60f3164..6084ca613d10ed 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -82,17 +82,19 @@ export function registerJobInfoRoutes( const { docId } = request.params; return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( - ({ _source: job }: JobSource): JobDocOutput => { - if (!job) { + (result): JobDocOutput => { + if (!result) { throw boom.notFound(); } + const { + _source: { jobtype: jobType, output: jobOutput }, + } = result; - const { jobtype: jobType } = job; if (!request.pre.management.jobTypes.includes(jobType)) { throw boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } - return job.output; + return jobOutput; } ); }, @@ -107,26 +109,25 @@ export function registerJobInfoRoutes( const request = makeRequestFacade(legacyRequest); const { docId } = request.params; - return jobsQuery - .get(request.pre.user, docId) - .then(({ _source: job }: JobSource): JobSource['_source'] => { - if (!job) { - throw boom.notFound(); - } + return jobsQuery.get(request.pre.user, docId).then((result): JobSource['_source'] => { + if (!result) { + throw boom.notFound(); + } - const { jobtype: jobType, payload } = job; - if (!request.pre.management.jobTypes.includes(jobType)) { - throw boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); - } + const { _source: job } = result; + const { jobtype: jobType, payload: jobPayload } = job; + if (!request.pre.management.jobTypes.includes(jobType)) { + throw boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } - return { - ...job, - payload: { - ...payload, - headers: undefined, - }, - }; - }); + return { + ...job, + payload: { + ...jobPayload, + headers: undefined, + }, + }; + }); }, }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts similarity index 60% rename from x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js rename to x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 79a5f1a4f6f5fe..eb473e0bc76d45 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -4,16 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; +import Boom from 'boom'; +import { Legacy } from 'kibana'; +import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { getUserFactory } from '../../lib/get_user'; +import { ServerFacade } from '../../../types'; const superuserRole = 'superuser'; -export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn(server) { +export type PreRoutingFunction = ( + request: Legacy.Request +) => Promise | AuthenticatedUser | null>; + +export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( + server: ServerFacade +) { const getUser = getUserFactory(server); const config = server.config(); - return async function authorizedUserPreRouting(request) { + return async function authorizedUserPreRouting(request: Legacy.Request) { const xpackInfo = server.plugins.xpack_main.info; if (!xpackInfo || !xpackInfo.isAvailable()) { @@ -21,7 +30,7 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting ['reporting', 'authorizedUserPreRouting', 'debug'], 'Unable to authorize user before xpack info is available.' ); - return boom.notFound(); + return Boom.notFound(); } const security = xpackInfo.feature('security'); @@ -32,12 +41,15 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting const user = await getUser(request); if (!user) { - return boom.unauthorized(`Sorry, you aren't authenticated`); + return Boom.unauthorized(`Sorry, you aren't authenticated`); } - const authorizedRoles = [superuserRole, ...config.get('xpack.reporting.roles.allow')]; + const authorizedRoles = [ + superuserRole, + ...(config.get('xpack.reporting.roles.allow') as string[]), + ]; if (!user.roles.find(role => authorizedRoles.includes(role))) { - return boom.forbidden(`Sorry, you don't have access to Reporting`); + return Boom.forbidden(`Sorry, you don't have access to Reporting`); } return user; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts similarity index 76% rename from x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js rename to x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts index 92973e3d0b4220..6efac818981efa 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts @@ -5,14 +5,20 @@ */ import Boom from 'boom'; +import { Legacy } from 'kibana'; +import { ServerFacade } from '../../../types'; -export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn(server) { +export type GetReportingFeatureIdFn = (request: Legacy.Request) => string; + +export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn( + server: ServerFacade +) { const xpackMainPlugin = server.plugins.xpack_main; const pluginId = 'reporting'; // License checking and enable/disable logic - return function reportingFeaturePreRouting(getReportingFeatureId) { - return function licensePreRouting(request) { + return function reportingFeaturePreRouting(getReportingFeatureId: GetReportingFeatureIdFn) { + return function licensePreRouting(request: Legacy.Request) { const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults(); const reportingFeatureId = getReportingFeatureId(request); const reportingFeature = licenseCheckResults[reportingFeatureId]; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts index 29ded68d403c58..caf24bf64f6023 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts @@ -6,11 +6,10 @@ import Joi from 'joi'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { ServerFacade, RequestFacade } from '../../../types'; -// @ts-ignore +import { ServerFacade } from '../../../types'; import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; -// @ts-ignore import { reportingFeaturePreRoutingFactory } from './reporting_feature_pre_routing'; +import { GetReportingFeatureIdFn } from './reporting_feature_pre_routing'; const API_TAG = 'api'; @@ -22,19 +21,16 @@ export interface RouteConfigFactory { }; } -type GetFeatureFunction = (request: RequestFacade) => any; -type PreRoutingFunction = (getFeatureId?: GetFeatureFunction) => any; - export type GetRouteConfigFactoryFn = ( - getFeatureId?: GetFeatureFunction | undefined + getFeatureId?: GetReportingFeatureIdFn ) => RouteConfigFactory; export function getRouteConfigFactoryReportingPre(server: ServerFacade): GetRouteConfigFactoryFn { - const authorizedUserPreRouting: PreRoutingFunction = authorizedUserPreRoutingFactory(server); - const reportingFeaturePreRouting: PreRoutingFunction = reportingFeaturePreRoutingFactory(server); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); + const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); - return (getFeatureId?: GetFeatureFunction): RouteConfigFactory => { - const preRouting = [{ method: authorizedUserPreRouting, assign: 'user' }]; + return (getFeatureId?: GetReportingFeatureIdFn): RouteConfigFactory => { + const preRouting: any[] = [{ method: authorizedUserPreRouting, assign: 'user' }]; if (getFeatureId) { preRouting.push(reportingFeaturePreRouting(getFeatureId)); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 9fae60afee4e82..47f384250dd537 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -355,3 +355,9 @@ export interface InterceptedRequest { frameId: string; resourceType: string; } + +export interface FieldFormats { + getConfig: number; + getInstance: (config: any) => any; + getDefaultInstance: (key: string) => any; +} From 5344702246aa74a9e85a373a883748c862c50f6b Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 20:31:11 -0500 Subject: [PATCH 42/59] [SIEM] Detections create prepackage rules (#55403) * update extra action on rule detail to match design * remove experimental label * allow pre-package to be deleted + do not allow wrong user to create pre-packages rules * Additional look back minimum value to 1 * fix flow with edit rule * add success toaster when rule is created or updated * Fix Timeline selector loading * review ben doc + change detectin engine to detection even in url * Succeeded text size consistency in rule details page * fix description of threats * fix test * fix type * fix internatinalization * adding pre-packaged rules * fix bug + enhance ux * unified icon * fix i18n * fix bugs * review I * review II * add border back --- .../legacy/plugins/siem/common/constants.ts | 3 +- .../containers/detection_engine/rules/api.ts | 41 +- .../detection_engine/rules/index.ts | 1 + .../detection_engine/rules/translations.ts | 14 + .../rules/use_create_packaged_rules.tsx | 81 ---- .../rules/use_pre_packaged_rules.tsx | 166 +++++++ .../detection_engine/rules/use_rules.tsx | 19 +- .../signals/use_privilege_user.tsx | 1 + .../components/user_info/index.tsx | 9 - .../detection_engine/detection_engine.tsx | 34 +- .../detection_engine/rules/all/actions.tsx | 7 +- .../detection_engine/rules/all/index.tsx | 445 ++++++++++-------- .../detection_engine/rules/all/reducer.ts | 36 +- .../pre_packaged_rules/load_empty_prompt.tsx | 65 +++ .../pre_packaged_rules/translations.ts | 57 +++ .../pre_packaged_rules/update_callout.tsx | 31 ++ .../detection_engine/rules/create/index.tsx | 30 +- .../detection_engine/rules/details/index.tsx | 16 +- .../detection_engine/rules/edit/index.tsx | 19 +- .../pages/detection_engine/rules/helpers.tsx | 46 ++ .../pages/detection_engine/rules/index.tsx | 99 +++- .../detection_engine/rules/translations.ts | 14 + 22 files changed, 870 insertions(+), 364 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index e67b533e46bce1..03824dd4e39602 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -60,7 +60,8 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`; -export const DETECTION_ENGINE_RULES_STATUS = `${DETECTION_ENGINE_URL}/rules/_find_statuses`; +export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`; +export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`; /** * Default signals index key for kibana.dev.yml diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a83e874437c101..6c9fd5c0ff3bc7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -26,7 +26,8 @@ import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_STATUS, + DETECTION_ENGINE_RULES_STATUS_URL, + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, } from '../../../../common/constants'; import * as i18n from '../../../pages/detection_engine/rules/translations'; @@ -63,7 +64,7 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise export const fetchRules = async ({ filterOptions = { filter: '', - sortField: 'enabled', + sortField: 'name', sortOrder: 'desc', }, pagination = { @@ -313,6 +314,7 @@ export const exportRules = async ({ * Get Rule Status provided Rule ID * * @param id string of Rule ID's (not rule_id) + * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ @@ -324,7 +326,7 @@ export const getRuleStatusById = async ({ signal: AbortSignal; }): Promise> => { const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS_URL}?ids=${encodeURIComponent( JSON.stringify([id]) )}`, { @@ -341,3 +343,36 @@ export const getRuleStatusById = async ({ await throwIfNotOk(response); return response.json(); }; + +/** + * Get pre packaged rules Status + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getPrePackagedRulesStatus = async ({ + signal, +}: { + signal: AbortSignal; +}): Promise<{ + rules_installed: number; + rules_not_installed: number; + rules_not_updated: number; +}> => { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL}`, + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + } + ); + + await throwIfNotOk(response); + return response.json(); +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts index e9a0f27b346960..c7ecfb33cd9052 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts @@ -10,4 +10,5 @@ export * from './persist_rule'; export * from './types'; export * from './use_rule'; export * from './use_rules'; +export * from './use_pre_packaged_rules'; export * from './use_rule_status'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts index 39efbde2ad5c24..a493e471a9bf6f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts @@ -16,3 +16,17 @@ export const RULE_ADD_FAILURE = i18n.translate( defaultMessage: 'Failed to add Rule', } ); + +export const RULE_PREPACKAGED_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription', + { + defaultMessage: 'Failed to installed pre-packaged rules from elastic', + } +); + +export const RULE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules from elastic', + } +); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx deleted file mode 100644 index 592419f8790113..00000000000000 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; - -import { createPrepackagedRules } from './api'; - -type Return = [boolean, boolean | null]; - -interface UseCreatePackagedRules { - canUserCRUD: boolean | null; - hasIndexManage: boolean | null; - hasManageApiKey: boolean | null; - isAuthenticated: boolean | null; - isSignalIndexExists: boolean | null; -} - -/** - * Hook for creating the packages rules - * - * @param canUserCRUD boolean - * @param hasIndexManage boolean - * @param hasManageApiKey boolean - * @param isAuthenticated boolean - * @param isSignalIndexExists boolean - * - * @returns [loading, hasCreatedPackageRules] - */ -export const useCreatePackagedRules = ({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, -}: UseCreatePackagedRules): Return => { - const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setLoading(true); - - async function createRules() { - try { - await createPrepackagedRules({ - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setHasCreatedPackageRules(true); - } - } catch (error) { - if (isSubscribed) { - setHasCreatedPackageRules(false); - } - } - if (isSubscribed) { - setLoading(false); - } - } - if ( - canUserCRUD && - hasIndexManage && - hasManageApiKey && - isAuthenticated && - isSignalIndexExists - ) { - createRules(); - } - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); - - return [loading, hasCreatedPackageRules]; -}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx new file mode 100644 index 00000000000000..abb43f3570e6c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useRef } from 'react'; + +import { useStateToaster, displaySuccessToast } from '../../../components/toasters'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; +import * as i18n from './translations'; + +type Func = () => void; +export type CreatePreBuiltRules = () => Promise; +interface Return { + createPrePackagedRules: null | CreatePreBuiltRules; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: Func | null; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; +} + +interface UsePrePackagedRuleProps { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasManageApiKey: boolean | null; + isAuthenticated: boolean | null; + isSignalIndexExists: boolean | null; +} + +/** + * Hook for using to get status about pre-packaged Rules from the Detection Engine API + * + * @param hasIndexManage boolean + * @param hasManageApiKey boolean + * @param isAuthenticated boolean + * @param isSignalIndexExists boolean + * + */ +export const usePrePackagedRules = ({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, +}: UsePrePackagedRuleProps): Return => { + const [rulesInstalled, setRulesInstalled] = useState(null); + const [rulesNotInstalled, setRulesNotInstalled] = useState(null); + const [rulesNotUpdated, setRulesNotUpdated] = useState(null); + const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); + const [loading, setLoading] = useState(true); + const createPrePackagedRules = useRef(null); + const refetchPrePackagedRules = useRef(null); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchPrePackagedRules = async () => { + try { + setLoading(true); + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + } + } catch (error) { + if (isSubscribed) { + setRulesInstalled(null); + setRulesNotInstalled(null); + setRulesNotUpdated(null); + errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + }; + + const createElasticRules = async (): Promise => { + return new Promise(async resolve => { + try { + if ( + canUserCRUD && + hasIndexManage && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + setLoadingCreatePrePackagedRules(true); + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + let iterationTryOfFetchingPrePackagedCount = 0; + let timeoutId = -1; + const stopTimeOut = () => { + if (timeoutId !== -1) { + window.clearTimeout(timeoutId); + } + }; + const reFetch = () => + window.setTimeout(async () => { + iterationTryOfFetchingPrePackagedCount = + iterationTryOfFetchingPrePackagedCount + 1; + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + if ( + isSubscribed && + ((prePackagedRuleStatusResponse.rules_not_installed === 0 && + prePackagedRuleStatusResponse.rules_not_updated === 0) || + iterationTryOfFetchingPrePackagedCount > 100) + ) { + setLoadingCreatePrePackagedRules(false); + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + stopTimeOut(); + resolve(true); + } else { + timeoutId = reFetch(); + } + }, 300); + timeoutId = reFetch(); + } + } + } catch (error) { + if (isSubscribed) { + setLoadingCreatePrePackagedRules(false); + errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); + resolve(false); + } + } + }); + }; + + fetchPrePackagedRules(); + createPrePackagedRules.current = createElasticRules; + refetchPrePackagedRules.current = fetchPrePackagedRules; + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + + return { + loading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus: refetchPrePackagedRules.current, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + createPrePackagedRules: createPrePackagedRules.current, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index b49dd8d51d4f75..04475ab1bc1787 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; @@ -12,36 +12,33 @@ import { fetchRules } from './api'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import * as i18n from './translations'; -type Return = [boolean, FetchRulesResponse]; +type Func = () => void; +type Return = [boolean, FetchRulesResponse, Func | null]; /** * Hook for using the list of Rules from the Detection Engine API * * @param pagination desired pagination options (e.g. page/perPage) * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) - * @param refetchToggle toggle for refetching data */ -export const useRules = ( - pagination: PaginationOptions, - filterOptions: FilterOptions, - refetchToggle: boolean -): Return => { +export const useRules = (pagination: PaginationOptions, filterOptions: FilterOptions): Return => { const [rules, setRules] = useState({ page: 1, perPage: 20, total: 0, data: [], }); + const reFetchRules = useRef(null); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - setLoading(true); async function fetchData() { try { + setLoading(true); const fetchRulesResult = await fetchRules({ filterOptions, pagination, @@ -62,12 +59,12 @@ export const useRules = ( } fetchData(); + reFetchRules.current = fetchData; return () => { isSubscribed = false; abortCtrl.abort(); }; }, [ - refetchToggle, pagination.page, pagination.perPage, filterOptions.filter, @@ -75,5 +72,5 @@ export const useRules = ( filterOptions.sortOrder, ]); - return [loading, rules]; + return [loading, rules, reFetchRules.current]; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index 7d0e331200d55e..564cf224a9fc8e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -59,6 +59,7 @@ export const usePrivilegeUser = (): Return => { setAuthenticated(false); setHasIndexManage(false); setHasIndexWrite(false); + setHasManageApiKey(false); errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); } } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 24e14473d40e98..bbaccb7882484b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,7 +10,6 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; -import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -162,14 +161,6 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); - useCreatePackagedRules({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, - }); - const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 5586749ce38d83..0e37740fe4a3ce 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -90,23 +90,6 @@ const DetectionEngineComponent = React.memo( [setAbsoluteRangeDatePicker] ); - if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { - return ( - - - - - ); - } - if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { - return ( - - - - - ); - } - const tabs = useMemo( () => ( @@ -125,6 +108,23 @@ const DetectionEngineComponent = React.memo( [detectionsTabs, tabName] ); + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + + ); + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + + ); + } + return ( <> {hasIndexWrite != null && !hasIndexWrite && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 435edcab433b6c..d6bf8643fff1ce 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -64,13 +64,12 @@ export const deleteRulesAction = async ( onRuleDeleted?: () => void ) => { try { - dispatch({ type: 'updateLoading', ids, isLoading: true }); + dispatch({ type: 'loading', isLoading: true }); const response = await deleteRules({ ids }); - const { rules, errors } = bucketRulesResponse(response); - - dispatch({ type: 'deleteRules', rules }); + const { errors } = bucketRulesResponse(response); + dispatch({ type: 'refresh' }); if (errors.length > 0) { displayErrorToast( i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 4aa6b778582f97..677033b258837d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -11,10 +11,12 @@ import { EuiLoadingContent, EuiSpacer, } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { useHistory } from 'react-router-dom'; - import uuid from 'uuid'; + +import { useRules, CreatePreBuiltRules } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import { UtilityBar, @@ -23,16 +25,17 @@ import { UtilityBarSection, UtilityBarText, } from '../../../../components/detection_engine/utility_bar'; -import { getColumns } from './columns'; -import { useRules } from '../../../../containers/detection_engine/rules'; +import { useStateToaster } from '../../../../components/toasters'; import { Loader } from '../../../../components/loader'; import { Panel } from '../../../../components/panel'; -import { getBatchItems } from './batch_actions'; +import { PrePackagedRulesPrompt } from '../components/pre_packaged_rules/load_empty_prompt'; +import { RuleDownloader } from '../components/rule_downloader'; +import { getPrePackagedRuleStatus } from '../helpers'; +import * as i18n from '../translations'; import { EuiBasicTableOnChange, TableData } from '../types'; +import { getBatchItems } from './batch_actions'; +import { getColumns } from './columns'; import { allRulesReducer, State } from './reducer'; -import * as i18n from '../translations'; -import { RuleDownloader } from '../components/rule_downloader'; -import { useStateToaster } from '../../../../components/toasters'; const initialState: State = { isLoading: true, @@ -52,6 +55,19 @@ const initialState: State = { }, }; +interface AllRulesProps { + createPrePackagedRules: CreatePreBuiltRules | null; + hasNoPermissions: boolean; + importCompleteToggle: boolean; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: () => void; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; + setRefreshRulesData: (refreshRule: () => void) => void; +} + /** * Table Component for displaying all Rules for a given cluster. Provides the ability to filter * by name, sort by enabled, and perform the following actions: @@ -60,191 +76,248 @@ const initialState: State = { * * Delete * * Import/Export */ -export const AllRules = React.memo<{ - hasNoPermissions: boolean; - importCompleteToggle: boolean; - loading: boolean; -}>(({ hasNoPermissions, importCompleteToggle, loading }) => { - const [ - { - exportPayload, - filterOptions, - isLoading, - refreshToggle, - selectedItems, - tableData, - pagination, - }, - dispatch, - ] = useReducer(allRulesReducer, initialState); - const history = useHistory(); - const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [, dispatchToaster] = useStateToaster(); - - const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [selectedItems, dispatch, dispatchToaster, history] - ); - - const tableOnChangeCallback = useCallback( - ({ page, sort }: EuiBasicTableOnChange) => { - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: page.index + 1, perPage: page.size }, - }); +export const AllRules = React.memo( + ({ + createPrePackagedRules, + hasNoPermissions, + importCompleteToggle, + loading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + setRefreshRulesData, + }) => { + const [ + { + exportPayload, + filterOptions, + isLoading, + refreshToggle, + selectedItems, + tableData, + pagination, + }, + dispatch, + ] = useReducer(allRulesReducer, initialState); + const history = useHistory(); + const [isInitialLoad, setIsInitialLoad] = useState(true); + const [isGlobalLoading, setIsGlobalLoad] = useState(false); + const [, dispatchToaster] = useStateToaster(); + const [isLoadingRules, rulesData, reFetchRulesData] = useRules(pagination, filterOptions); + + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedItems, dispatch, dispatchToaster, history] + ); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: page.index + 1, perPage: page.size }, + }); + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + sortField: 'enabled', // Only enabled is supported for sorting currently + sortOrder: sort?.direction ?? 'desc', + }, + }); + }, + [dispatch, filterOptions, pagination] + ); + + const columns = useMemo(() => { + return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); + }, [dispatch, dispatchToaster, history]); + + useEffect(() => { + dispatch({ type: 'loading', isLoading: isLoadingRules }); + }, [isLoadingRules]); + + useEffect(() => { + if (!isLoadingRules && !loading && isInitialLoad) { + setIsInitialLoad(false); + } + }, [isInitialLoad, isLoadingRules, loading]); + + useEffect(() => { + if (!isGlobalLoading && (isLoadingRules || isLoading)) { + setIsGlobalLoad(true); + } else if (isGlobalLoading && !isLoadingRules && !isLoading) { + setIsGlobalLoad(false); + } + }, [setIsGlobalLoad, isGlobalLoading, isLoadingRules, isLoading]); + + useEffect(() => { + if (!isInitialLoad) { + dispatch({ type: 'refresh' }); + } + }, [importCompleteToggle]); + + useEffect(() => { + if (reFetchRulesData != null) { + reFetchRulesData(); + } + refetchPrePackagedRulesStatus(); + }, [refreshToggle, reFetchRulesData, refetchPrePackagedRulesStatus]); + + useEffect(() => { + if (reFetchRulesData != null) { + setRefreshRulesData(reFetchRulesData); + } + }, [reFetchRulesData, setRefreshRulesData]); + + useEffect(() => { dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - sortField: 'enabled', // Only enabled is supported for sorting currently - sortOrder: sort?.direction ?? 'desc', + type: 'updateRules', + rules: rulesData.data, + pagination: { + page: rulesData.page, + perPage: rulesData.perPage, + total: rulesData.total, }, }); - }, - [dispatch, filterOptions, pagination] - ); - - const columns = useMemo(() => { - return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); - }, [dispatch, dispatchToaster, history]); - - useEffect(() => { - dispatch({ type: 'loading', isLoading: isLoadingRules }); - - if (!isLoadingRules) { - setIsInitialLoad(false); - } - }, [isLoadingRules]); - - useEffect(() => { - if (!isInitialLoad) { - dispatch({ type: 'refresh' }); - } - }, [importCompleteToggle]); - - useEffect(() => { - dispatch({ - type: 'updateRules', - rules: rulesData.data, - pagination: { - page: rulesData.page, - perPage: rulesData.perPage, - total: rulesData.total, - }, - }); - }, [rulesData]); - - const euiBasicTableSelectionProps = useMemo( - () => ({ - selectable: (item: TableData) => !item.isLoading, - onSelectionChange: (selected: TableData[]) => - dispatch({ type: 'setSelected', selectedItems: selected }), - }), - [] - ); - - return ( - <> - { - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), - color: 'success', - iconType: 'check', - }, - }); - }} - /> - - - - {isInitialLoad ? ( - - ) : ( + }, [rulesData]); + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + dispatch({ type: 'refresh' }); + } + }, [createPrePackagedRules]); + + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: TableData) => !item.isLoading, + onSelectionChange: (selected: TableData[]) => + dispatch({ type: 'setSelected', selectedItems: selected }), + }), + [] + ); + + return ( + <> + { + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }} + /> + + + <> - - { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - filter: filterString, - }, - }); - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: 1 }, - }); - }} - /> - - - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - - - - {i18n.SELECTED_RULES(selectedItems.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} - dispatch({ type: 'refresh' })} - > - {i18n.REFRESH} - - - - - - - {(isLoading || loading) && ( + {rulesInstalled != null && rulesInstalled > 0 && ( + + { + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + filter: filterString, + }, + }); + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: 1 }, + }); + }} + /> + + )} + {isInitialLoad && isEmpty(tableData) && ( + + )} + {isGlobalLoading && !isEmpty(tableData) && ( )} + {isEmpty(tableData) && prePackagedRuleStatus === 'ruleNotInstalled' && ( + + )} + {!isEmpty(tableData) && ( + <> + + + + {i18n.SHOWING_RULES(pagination.total ?? 0)} + + + + {i18n.SELECTED_RULES(selectedItems.length)} + {!hasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + dispatch({ type: 'refresh' })} + > + {i18n.REFRESH} + + + + + + + + )} - )} - - - ); -}); + + + ); + } +); AllRules.displayName = 'AllRules'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts index 22d6ca2195fe66..74ce8f2847faa3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts @@ -58,19 +58,21 @@ export const allRulesReducer = (state: State, action: Action): State => { const ruleIds = state.rules.map(r => r.rule_id); const appendIdx = action.appendRuleId != null ? state.rules.findIndex(r => r.id === action.appendRuleId) : -1; - const updatedRules = action.rules.reduce( - (rules, updatedRule) => - ruleIds.includes(updatedRule.rule_id) - ? rules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)) - : appendIdx !== -1 - ? [ - ...rules.slice(0, appendIdx + 1), - updatedRule, - ...rules.slice(appendIdx + 1, rules.length - 1), - ] - : [...rules, updatedRule], - [...state.rules] - ); + const updatedRules = action.rules.reverse().reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.rule_id)) { + newRules = newRules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)); + } else if (appendIdx !== -1) { + newRules = [ + ...newRules.slice(0, appendIdx + 1), + updatedRule, + ...newRules.slice(appendIdx + 1, newRules.length), + ]; + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); // Update enabled on selectedItems so that batch actions show correct available actions const updatedRuleIdToState = action.rules.reduce>( @@ -88,6 +90,13 @@ export const allRulesReducer = (state: State, action: Action): State => { rules: updatedRules, tableData: formatRules(updatedRules), selectedItems: updatedSelectedItems, + pagination: { + ...state.pagination, + total: + action.appendRuleId != null + ? state.pagination.total + action.rules.length + : state.pagination.total, + }, }; } case 'updatePagination': { @@ -112,6 +121,7 @@ export const allRulesReducer = (state: State, action: Action): State => { ...state, rules: updatedRules, tableData: formatRules(updatedRules), + refreshToggle: !state.refreshToggle, }; } case 'setSelected': { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx new file mode 100644 index 00000000000000..41b7fafd6becdb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; +import * as i18n from './translations'; + +const EmptyPrompt = styled(EuiEmptyPrompt)` + align-self: center; /* Corrects horizontal centering in IE11 */ +`; + +interface PrePackagedRulesPromptProps { + createPrePackagedRules: () => void; + loading: boolean; + userHasNoPermissions: boolean; +} + +const PrePackagedRulesPromptComponent: React.FC = ({ + createPrePackagedRules, + loading = false, + userHasNoPermissions = true, +}) => { + const handlePreBuiltCreation = useCallback(() => { + createPrePackagedRules(); + }, [createPrePackagedRules]); + return ( + {i18n.PRE_BUILT_TITLE}} + body={

{i18n.PRE_BUILT_MSG}

} + actions={ + + + + {i18n.PRE_BUILT_ACTION} + + + + + {i18n.CREATE_RULE_ACTION} + + + + } + /> + ); +}; + +export const PrePackagedRulesPrompt = memo(PrePackagedRulesPromptComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts new file mode 100644 index 00000000000000..5f89bd072ebed8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PRE_BUILT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptTitle', + { + defaultMessage: 'Load Elastic prebuilt detection rules', + } +); + +export const PRE_BUILT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', + { + defaultMessage: + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met.By default, all prebuilt rules are disabled and you select which rules you want to activate', + } +); + +export const PRE_BUILT_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', + { + defaultMessage: 'Load prebuilt detection rules', + } +); + +export const CREATE_RULE_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.createOwnRuletButton', + { + defaultMessage: 'Create your own rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.updatePrePackagedRulesTitle', + { + defaultMessage: 'Update available for Elastic prebuilt rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_MSG = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesMsg', { + values: { updateRules }, + defaultMessage: + 'You can update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}}. Note that this will reload deleted Elastic prebuilt rules.', + }); + +export const UPDATE_PREPACKAGED_RULES = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesButton', { + values: { updateRules }, + defaultMessage: + 'Update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}} ', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx new file mode 100644 index 00000000000000..80a120ebc63ef0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import * as i18n from './translations'; + +interface UpdatePrePackagedRulesCallOutProps { + loading: boolean; + numberOfUpdatedRules: number; + updateRules: () => void; +} + +const UpdatePrePackagedRulesCallOutComponent: React.FC = ({ + loading, + numberOfUpdatedRules, + updateRules, +}) => ( + +

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

+ + {i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)} + +
+); + +export const UpdatePrePackagedRulesCallOut = memo(UpdatePrePackagedRulesCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index cbc60015d9c875..6eaaf37c066892 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -29,10 +29,10 @@ import * as i18n from './translations'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; const MyEuiPanel = styled(EuiPanel)<{ - zIndex?: number; + zindex?: number; }>` position: relative; - z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + z-index: ${props => props.zindex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ .euiAccordion__iconWrapper { display: none; @@ -80,16 +80,6 @@ export const CreateRuleComponent = React.memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } - const setStepData = useCallback( (step: RuleStep, data: unknown, isValid: boolean) => { stepsData.current[step] = { ...stepsData.current[step], data, isValid }; @@ -228,6 +218,16 @@ export const CreateRuleComponent = React.memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> @@ -237,7 +237,7 @@ export const CreateRuleComponent = React.memo(() => { isLoading={isLoading || loading} title={i18n.PAGE_TITLE} /> - + { - + { - + ( const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } - const title = isLoading === true || rule === null ? : rule.name; const subTitle = useMemo( () => @@ -228,6 +220,14 @@ const RuleDetailsComponent = memo( [ruleEnabled, setRuleEnabled] ); + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } + return ( <> {hasIndexWrite != null && !hasIndexWrite && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index be56e916ae6c94..8c0b78e66847ea 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -62,15 +62,6 @@ export const EditRuleComponent = memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } const [initForm, setInitForm] = useState(false); const [myAboutRuleForm, setMyAboutRuleForm] = useState({ @@ -277,6 +268,16 @@ export const EditRuleComponent = memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index cfe6cb8da1cb09..4cbaa38e1febcc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -69,6 +69,52 @@ export const getStepsData = ({ export const useQuery = () => new URLSearchParams(useLocation().search); +export type PrePackagedRuleStatus = + | 'ruleInstalled' + | 'ruleNotInstalled' + | 'ruleNeedUpdate' + | 'someRuleUninstall' + | 'unknown'; + +export const getPrePackagedRuleStatus = ( + rulesInstalled: number | null, + rulesNotInstalled: number | null, + rulesNotUpdated: number | null +): PrePackagedRuleStatus => { + if ( + rulesNotInstalled != null && + rulesInstalled === 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'ruleNotInstalled'; + } else if ( + rulesInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled === 0 && + rulesNotUpdated === 0 + ) { + return 'ruleInstalled'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'someRuleUninstall'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesNotUpdated != null && + rulesInstalled > 0 && + rulesNotInstalled >= 0 && + rulesNotUpdated > 0 + ) { + return 'ruleNeedUpdate'; + } + return 'unknown'; +}; export const setFieldValue = ( form: FormHook, schema: FormSchema, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index dd46b33ca72578..5cdc7a1d4fa6bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -6,32 +6,81 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; import { getEmptyTagValue } from '../../../components/empty_value'; import { HeaderPage } from '../../../components/header_page'; import { WrapperPage } from '../../../components/wrapper_page'; import { SpyRoute } from '../../../utils/route/spy_routes'; - +import { useUserInfo } from '../components/user_info'; import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; import { ReadOnlyCallOut } from './components/read_only_callout'; -import { useUserInfo } from '../components/user_info'; +import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/update_callout'; +import { getPrePackagedRuleStatus } from './helpers'; import * as i18n from './translations'; +type Func = () => void; + export const RulesComponent = React.memo(() => { const [showImportModal, setShowImportModal] = useState(false); const [importCompleteToggle, setImportCompleteToggle] = useState(false); + const refreshRulesData = useRef(null); const { loading, isSignalIndexExists, isAuthenticated, canUserCRUD, + hasIndexManage, hasManageApiKey, } = useUserInfo(); + const { + createPrePackagedRules, + loading: prePackagedRuleLoading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + } = usePrePackagedRules({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isSignalIndexExists, + isAuthenticated, + }); + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + const lastCompletedRun = undefined; + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + if (refreshRulesData.current != null) { + refreshRulesData.current(); + } + } + }, [createPrePackagedRules, refreshRulesData]); + + const handleRefetchPrePackagedRulesStatus = useCallback(() => { + if (refetchPrePackagedRulesStatus != null) { + refetchPrePackagedRulesStatus(); + } + }, [refetchPrePackagedRulesStatus]); + + const handleSetRefreshRulesData = useCallback((refreshRule: Func) => { + refreshRulesData.current = refreshRule; + }, []); if ( isSignalIndexExists != null && @@ -40,9 +89,7 @@ export const RulesComponent = React.memo(() => { ) { return ; } - const userHasNoPermissions = - canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - const lastCompletedRun = undefined; + return ( <> {userHasNoPermissions && } @@ -73,6 +120,30 @@ export const RulesComponent = React.memo(() => { title={i18n.PAGE_TITLE} > + {prePackagedRuleStatus === 'ruleNotInstalled' && ( + + + {i18n.LOAD_PREPACKAGED_RULES} + + + )} + {prePackagedRuleStatus === 'someRuleUninstall' && ( + + + {i18n.RELOAD_MISSING_PREPACKAGED_RULES(rulesNotInstalled ?? 0)} + + + )} { + {prePackagedRuleStatus === 'ruleNeedUpdate' && ( + + )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index d144a6d56a1687..672aab8ef83140 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -310,3 +310,17 @@ export const UPDATE = i18n.translate('xpack.siem.detectionEngine.rules.updateBut export const DELETE = i18n.translate('xpack.siem.detectionEngine.rules.deleteDescription', { defaultMessage: 'Delete', }); + +export const LOAD_PREPACKAGED_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.loadPrePackagedRulesButton', + { + defaultMessage: 'Load Elastic prebuilt rules', + } +); + +export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.reloadMissingPrePackagedRulesButton', { + values: { missingRules }, + defaultMessage: + 'Reload {missingRules} deleted Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + }); From 8f0c413ef5285f4f6c9a9b1279b758704ae25eff Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 21 Jan 2020 19:02:28 -0700 Subject: [PATCH 43/59] [SIEM][Detection Engine] Critical blocker, fixes schema accepting values it should not (#55488) ## Summary * This fixes the schema accepting values the UI cannot handle at this point with severity. It's best to just set it to a small fixed enumeration of values. * From feedback from people the values should have more defaults and be more consistent in the schema so gave defaults for `from`, `to`, and `interval`. * Removed dead query examples that cannot happen because immutable cannot be set by end users anymore * Changes the version and other sections to be integer only and not allow floats * Added unit tests ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../rules/add_prepackaged_rules_route.test.ts | 2 +- .../get_prepackaged_rule_status_route.test.ts | 2 +- .../add_prepackaged_rules_schema.test.ts | 164 ++++++++++++------ .../schemas/add_prepackaged_rules_schema.ts | 4 +- .../schemas/create_rules_bulk_schema.test.ts | 68 +++++++- .../schemas/create_rules_schema.test.ts | 154 ++++++++++------ .../routes/schemas/create_rules_schema.ts | 4 +- .../schemas/import_rules_schema.test.ts | 163 +++++++++++------ .../routes/schemas/import_rules_schema.ts | 4 +- .../schemas/query_signals_index_schema.ts | 2 +- .../routes/schemas/schemas.ts | 13 +- .../schemas/update_rules_schema.test.ts | 102 ++++++----- .../create_rules_stream_from_ndjson.test.ts | 22 +-- .../scripts/rules/queries/query_disabled.json | 2 - .../rules/queries/query_immutable.json | 11 -- .../scripts/rules/queries/query_lucene.json | 2 - .../rules/queries/query_mitre_attack.json | 2 - .../rules/queries/query_timelineid.json | 2 - .../rules/queries/query_with_filter.json | 2 - .../rules/queries/query_with_meta_data.json | 2 - .../rules/queries/query_with_rule_id.json | 2 - .../rules/queries/query_with_tags.json | 2 - .../rules/queries/simplest_filters.json | 2 - .../scripts/rules/queries/simplest_query.json | 2 - .../saved_queries/saved_query_by_rule_id.json | 2 - .../saved_query_with_filters.json | 2 - .../saved_queries/saved_query_with_query.json | 2 - .../saved_query_with_query_filter.json | 2 - .../saved_queries/simplest_saved_query.json | 2 - 29 files changed, 480 insertions(+), 265 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index ed193b6473a9e7..a99893433ea8df 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -34,7 +34,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 2, // set one higher than the mocks which is set to 1 to trigger updates diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts index 1ae9e87b8eefe7..f07d6a9fc65a6f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts @@ -33,7 +33,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 2, // set one higher than the mocks which is set to 1 to trigger updates diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index abdd5a0c7b5084..2a04c15b8cd9fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -78,7 +78,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -91,7 +91,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -105,7 +105,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -120,7 +120,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -137,7 +137,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -156,7 +156,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -175,7 +175,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -196,7 +196,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -215,7 +215,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -234,7 +234,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -256,7 +256,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -291,7 +291,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -312,7 +312,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -332,7 +332,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -353,7 +353,7 @@ describe('add prepackaged rules schema', () => { index: ['index-1'], immutable: false, name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -374,7 +374,7 @@ describe('add prepackaged rules schema', () => { index: ['index-1'], immutable: true, name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -394,7 +394,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -413,7 +413,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -435,7 +435,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -460,7 +460,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -482,7 +482,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', version: 1, }).value.interval @@ -499,7 +499,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 1, @@ -517,7 +517,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', version: 1, @@ -535,7 +535,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -554,7 +554,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -576,7 +576,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -596,7 +596,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -617,7 +617,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -638,7 +638,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -659,7 +659,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -681,7 +681,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -703,7 +703,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -725,7 +725,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -750,7 +750,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -779,7 +779,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -823,7 +823,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -863,7 +863,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -898,7 +898,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -923,7 +923,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -948,7 +948,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -973,7 +973,7 @@ describe('add prepackaged rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -996,7 +996,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1019,7 +1019,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1042,7 +1042,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1065,7 +1065,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1088,7 +1088,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1116,7 +1116,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1140,7 +1140,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1162,7 +1162,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1186,7 +1186,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1210,7 +1210,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1235,7 +1235,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1260,7 +1260,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1285,7 +1285,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1297,4 +1297,60 @@ describe('add prepackaged rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts index 9311371d630f78..d254f832434918 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts @@ -51,7 +51,7 @@ export const addPrepackagedRulesSchema = Joi.object({ enabled: enabled.default(false), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id: rule_id.required(), immutable: immutable.default(true).valid(true), index, @@ -71,7 +71,7 @@ export const addPrepackagedRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts index 17fb5320daa013..1eab50848b822a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts @@ -37,7 +37,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -57,7 +57,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -70,7 +70,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -79,4 +79,66 @@ describe('create_rules_bulk_schema', () => { ]).error ).toBeFalsy(); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).value[0].from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).value[0].to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).error.message + ).toEqual( + '"value" at position 0 fails because [child "severity" fails because ["severity" must be one of [low, medium, high, critical]]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index c76071047434c6..f765f01300c583 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -79,7 +79,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -92,7 +92,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -106,7 +106,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -121,7 +121,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -138,7 +138,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -156,7 +156,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -175,7 +175,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -195,7 +195,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -213,7 +213,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -232,7 +232,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -250,7 +250,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -273,7 +273,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -308,7 +308,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -329,7 +329,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -351,7 +351,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -375,7 +375,7 @@ describe('create rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -398,7 +398,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual('5m'); @@ -415,7 +415,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -433,7 +433,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error.message @@ -451,7 +451,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -470,7 +470,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -492,7 +492,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -512,7 +512,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -533,7 +533,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -554,7 +554,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -575,7 +575,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -597,7 +597,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -619,7 +619,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -641,7 +641,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -664,7 +664,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -693,7 +693,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -737,7 +737,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -777,7 +777,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -812,7 +812,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -837,7 +837,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -864,7 +864,7 @@ describe('create rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -886,7 +886,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -908,7 +908,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -930,7 +930,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -952,7 +952,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -974,7 +974,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -999,7 +999,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1022,7 +1022,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1044,7 +1044,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1067,7 +1067,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1089,7 +1089,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1112,7 +1112,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1135,7 +1135,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1158,7 +1158,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1168,4 +1168,60 @@ describe('create rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts index 5d9972453fb1a2..06dbb0cbb48f3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -43,7 +43,7 @@ export const createRulesSchema = Joi.object({ enabled: enabled.default(true), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id, index, interval: interval.default('5m'), @@ -63,7 +63,7 @@ export const createRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index 20f418c57b5dbe..b19a91d18c3ff4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -84,7 +84,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -97,7 +97,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -111,7 +111,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -126,7 +126,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -143,7 +143,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -161,7 +161,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -180,7 +180,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -200,7 +200,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -218,7 +218,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -237,7 +237,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -255,7 +255,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -278,7 +278,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -313,7 +313,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -334,7 +334,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -356,7 +356,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -381,7 +381,7 @@ describe('import rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -403,7 +403,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual('5m'); @@ -420,7 +420,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -438,7 +438,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error.message @@ -456,7 +456,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -475,7 +475,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -497,7 +497,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -517,7 +517,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -538,7 +538,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -559,7 +559,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -580,7 +580,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -602,7 +602,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -624,7 +624,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -646,7 +646,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -670,7 +670,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -700,7 +700,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -744,7 +744,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -784,7 +784,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -819,7 +819,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -844,7 +844,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -869,7 +869,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -894,7 +894,7 @@ describe('import rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -917,7 +917,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -940,7 +940,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -963,7 +963,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -986,7 +986,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1009,7 +1009,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1035,7 +1035,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1059,7 +1059,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1081,7 +1081,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1104,7 +1104,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1126,7 +1126,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1149,7 +1149,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1174,7 +1174,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1197,7 +1197,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1219,7 +1219,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1240,7 +1240,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1265,7 +1265,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1290,7 +1290,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1344,4 +1344,59 @@ describe('import rules schema', () => { expect(importRulesPayloadSchema.validate({ file: {} }).error).toBeFalsy(); }); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts index df825c442fff65..8516585a2c0550 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts @@ -59,7 +59,7 @@ export const importRulesSchema = Joi.object({ enabled: enabled.default(true), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id: rule_id.required(), immutable: immutable.default(false), index, @@ -80,7 +80,7 @@ export const importRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts index 0a6fceb44f845a..26a32d2e4980b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts @@ -9,7 +9,7 @@ import Joi from 'joi'; export const querySignalsSchema = Joi.object({ query: Joi.object(), aggs: Joi.object(), - size: Joi.number(), + size: Joi.number().integer(), track_total_hits: Joi.boolean(), _source: Joi.array().items(Joi.string()), }).min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts index ecca661d2b8565..a027fcb96b599f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -37,12 +37,15 @@ export const timeline_title = Joi.string().when('timeline_id', { otherwise: Joi.forbidden(), }); export const meta = Joi.object(); -export const max_signals = Joi.number().greater(0); +export const max_signals = Joi.number() + .integer() + .greater(0); export const name = Joi.string(); export const risk_score = Joi.number() + .integer() .greater(-1) .less(101); -export const severity = Joi.string(); +export const severity = Joi.string().valid('low', 'medium', 'high', 'critical'); export const status = Joi.string().valid('open', 'closed'); export const to = Joi.string(); export const type = Joi.string().valid('query', 'saved_query'); @@ -51,9 +54,11 @@ export const references = Joi.array() .items(Joi.string()) .single(); export const per_page = Joi.number() + .integer() .min(0) .default(20); export const page = Joi.number() + .integer() .min(1) .default(1); export const signal_ids = Joi.array().items(Joi.string()); @@ -97,4 +102,6 @@ export const updated_at = Joi.string() .strict(); export const created_by = Joi.string(); export const updated_by = Joi.string(); -export const version = Joi.number().min(1); +export const version = Joi.number() + .integer() + .min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index 823ebb90a3b3cb..44b3b5b927be2c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -147,7 +147,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeFalsy(); }); @@ -160,7 +160,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeFalsy(); }); @@ -173,7 +173,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeFalsy(); @@ -187,7 +187,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeFalsy(); @@ -201,7 +201,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -216,7 +216,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -232,7 +232,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -248,7 +248,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -264,7 +264,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -281,7 +281,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -298,7 +298,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -316,7 +316,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -334,7 +334,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -350,7 +350,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -366,7 +366,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -385,7 +385,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -403,7 +403,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual(undefined); @@ -418,7 +418,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -436,7 +436,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -459,7 +459,7 @@ describe('update rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -479,7 +479,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error @@ -495,7 +495,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -512,7 +512,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -530,7 +530,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -549,7 +549,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -568,7 +568,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -587,7 +587,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -607,7 +607,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -627,7 +627,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -679,7 +679,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -699,7 +699,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -737,7 +737,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -778,7 +778,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -820,7 +820,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -858,7 +858,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -890,7 +890,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -909,7 +909,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -927,7 +927,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -946,7 +946,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -965,7 +965,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -984,7 +984,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -992,4 +992,24 @@ describe('update rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index fce3c90ef18e79..48b7195c3b0bcf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -21,7 +21,7 @@ export const getOutputSample = (): Partial => ({ to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }); @@ -55,7 +55,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -78,7 +78,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -120,7 +120,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -143,7 +143,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -184,7 +184,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -207,7 +207,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -248,7 +248,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -272,7 +272,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -312,7 +312,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -338,7 +338,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json index 38b3ed9f746967..f354351caa5f0d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "enabled": false } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json deleted file mode 100644 index 681d66e16d0ba0..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Query which is immutable", - "description": "Example query which is immutable", - "risk_score": 1, - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "immutable": true -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json index ed8849831a4793..03d5ab3c0b4e94 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "language": "lucene" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json index 721a172ce55d75..f728e3b9882064 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "threats": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json index eb87a14e0c6885..2bc4aa22759268 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "timeline_id": "timeline-id", "timeline_title": "timeline_title" diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json index c754ab73ea21e8..28ae121c7969ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json @@ -5,8 +5,6 @@ "risk_score": 15, "severity": "high", "type": "query", - "from": "now-24h", - "to": "now", "query": "user.name: root or user.name: admin", "filters": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json index f9f5bf854e45c1..8d86605d648ec6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "meta": { "whatever-you-want": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json index e4da1960075275..3347fb0e724b31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json @@ -5,7 +5,5 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json index 954f5942180d66..2c61f08d4b480d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json @@ -5,8 +5,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "tags": ["tag_1", "tag_2"] } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json index 61e68f886ffe72..37f0e541298b27 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json index e812b031a28fd0..407fa1dcc08847 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json @@ -4,7 +4,5 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json index 0e0be24c00207c..48b6a34cf2316b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json @@ -5,7 +5,5 @@ "risk_score": 5, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "saved_id": "test-saved-id" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json index 55f95e9644b8b3..2bb9845a507c3e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json index ee37c4cb784d1a..786dcdb377a684 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "saved_id": "test-saved-id" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json index 19801e7a98ac20..5e5c51d2243b00 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json @@ -5,8 +5,6 @@ "risk_score": 15, "severity": "high", "type": "query", - "from": "now-24h", - "to": "now", "query": "user.name: root or user.name: admin", "filters": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json index a3dbf0f1b09af6..ac9f224313b232 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json @@ -4,7 +4,5 @@ "risk_score": 5, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "saved_id": "test-saved-id" } From ccbb2863e7927b986eecfd1b4cae31233b137ea5 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 21 Jan 2020 18:54:54 -0800 Subject: [PATCH 44/59] [DOCS] Add tip for using elasticsearch-certutil http command (#55357) --- .../securing-communications/index.asciidoc | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/user/security/securing-communications/index.asciidoc b/docs/user/security/securing-communications/index.asciidoc index b370c35905bce4..3e022315a4d842 100644 --- a/docs/user/security/securing-communications/index.asciidoc +++ b/docs/user/security/securing-communications/index.asciidoc @@ -4,8 +4,9 @@ Encrypting communications ++++ -{kib} supports Transport Layer Security (TLS/SSL) encryption for all forms of data-in-transit. Browsers send traffic to {kib} and {kib} -sends traffic to {es}. These communications are configured separately. +{kib} supports Transport Layer Security (TLS/SSL) encryption for all forms of +data-in-transit. Browsers send traffic to {kib} and {kib} sends traffic to {es}. +These communications are configured separately. [[configuring-tls-browser-kib]] ==== Encrypting traffic between the browser and {kib} @@ -78,7 +79,8 @@ NOTE: To perform this step, you must {ref}/configuring-security.html[enable the {es} {security-features}] or you must have a proxy that provides an HTTPS endpoint for {es}. -. Specify the HTTPS URL in the `elasticsearch.hosts` setting in the {kib} configuration file, `kibana.yml`: +. Specify the HTTPS URL in the `elasticsearch.hosts` setting in the {kib} +configuration file, `kibana.yml`: + -- [source,yaml] @@ -86,7 +88,9 @@ must have a proxy that provides an HTTPS endpoint for {es}. elasticsearch.hosts: ["https://.com:9200"] -------------------------------------------------------------------------------- -Using the HTTPS protocol results in a default `elasticsearch.ssl.verificationMode` option of `full`, which utilizes hostname verification. +Using the HTTPS protocol results in a default +`elasticsearch.ssl.verificationMode` option of `full`, which utilizes hostname +verification. For more information, see <>. -- @@ -95,8 +99,10 @@ For more information, see <>. + -- -If you are using your own CA to sign certificates for {es}, then you need to specify the CA certificate chain in {kib} to properly establish -trust in TLS connections. If your CA certificate chain is contained in a PKCS #12 trust store, specify it like so: +If you are using your own CA to sign certificates for {es}, then you need to +specify the CA certificate chain in {kib} to properly establish trust in TLS +connections. If your CA certificate chain is contained in a PKCS #12 trust store, +specify it like so: [source,yaml] -------------------------------------------------------------------------------- @@ -104,15 +110,21 @@ elasticsearch.ssl.truststore.path: "/path/to/your/truststore.p12" elasticsearch.ssl.truststore.password: "optional decryption password" -------------------------------------------------------------------------------- -Otherwise, if your CA certificate chain is in PEM format, specify each certificate like so: +Otherwise, if your CA certificate chain is in PEM format, specify each +certificate like so: [source,yaml] -------------------------------------------------------------------------------- elasticsearch.ssl.certificateAuthorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] -------------------------------------------------------------------------------- +TIP: You can use the {ref}/certutil.html[`elasticsearch-certutil http` command] +to generate a PEM format x.509 certificate for the {es} CA. It also provides +detailed configuration details in readme files. + -- -. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to connect to the {es} monitoring cluster via HTTPS. The steps -are the same as above, but each setting is prefixed by `xpack.monitoring.`. For example, `xpack.monitoring.elasticsearch.hosts`, +. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to +connect to the {es} monitoring cluster via HTTPS. The steps are the same as +above, but each setting is prefixed by `xpack.monitoring.`. For example, `xpack.monitoring.elasticsearch.hosts`, `xpack.monitoring.elasticsearch.ssl.truststore.path`, etc. From 31e6a3b75a1342696304056e3eeaf59d29a79784 Mon Sep 17 00:00:00 2001 From: The SpaceCake Project Date: Tue, 21 Jan 2020 22:59:33 -0500 Subject: [PATCH 45/59] rules part deux (#55507) cuts and metadata additions --- ...den_file_attribute_with_via_attribexe.json | 41 +- .../eql_adobe_hijack_persistence.json | 26 +- .../eql_audio_capture_via_powershell.json | 26 +- .../eql_audio_capture_via_soundrecorder.json | 26 +- .../eql_bypass_uac_event_viewer.json | 26 +- .../eql_bypass_uac_via_cmstp.json | 26 +- .../eql_bypass_uac_via_sdclt.json | 26 +- .../eql_clearing_windows_event_logs.json | 26 +- ...delete_volume_usn_journal_with_fsutil.json | 26 +- ...deleting_backup_catalogs_with_wbadmin.json | 26 +- .../eql_direct_outbound_smb_connection.json | 26 +- ...ble_windows_firewall_rules_with_netsh.json | 26 +- .../eql_dll_search_order_hijack.json | 41 +- ...coding_or_decoding_files_via_certutil.json | 26 +- .../eql_local_scheduled_task_commands.json | 26 +- .../eql_local_service_commands.json | 26 +- ...ql_modification_of_boot_configuration.json | 28 +- ...ql_msbuild_making_network_connections.json | 26 +- .../eql_mshta_making_network_connections.json | 29 +- .../eql_msxsl_making_network_connections.json | 26 +- .../eql_psexec_lateral_movement_command.json | 7 +- ...ql_suspicious_ms_office_child_process.json | 26 +- ...l_suspicious_ms_outlook_child_process.json | 26 +- ...l_suspicious_pdf_reader_child_process.json | 26 +- .../eql_system_shells_via_services.json | 26 +- ...usual_network_connection_via_rundll32.json | 26 +- .../eql_unusual_parentchild_relationship.json | 26 +- ...ql_unusual_process_network_connection.json | 26 +- .../eql_user_account_creation.json | 24 +- ...eql_user_added_to_administrator_group.json | 26 +- ...ume_shadow_copy_deletion_via_vssadmin.json | 28 +- ..._volume_shadow_copy_deletion_via_wmic.json | 28 +- ...l_windows_script_executing_powershell.json | 24 +- .../eql_wmic_command_lateral_movement.json | 26 +- .../rules/prepackaged_rules/index.ts | 444 +++--------------- .../linux_kernel_module_activity.json | 37 +- ...nux_process_started_in_temp_directory.json | 17 +- .../linux_shell_activity_by_web_server.json | 33 +- .../linux_whoami_commmand.json | 32 +- ...ed_invokecommand_powershell_execution.json | 43 -- ...ncoded_newobject_powershell_execution.json | 43 -- ...ded_startprocess_powershell_execution.json | 43 -- ...gory_a_suspicious_string_was_detected.json | 17 - ...ttempted_administrator_privilege_gain.json | 17 - ..._category_attempted_denial_of_service.json | 17 - ...a_category_attempted_information_leak.json | 17 - ...empted_login_with_suspicious_username.json | 17 - ...ategory_attempted_user_privilege_gain.json | 17 - ...ta_category_client_using_unusual_port.json | 17 - ...egory_crypto_currency_mining_activity.json | 17 - ...icata_category_decode_of_an_rpc_query.json | 17 - ...t_username_and_password_login_attempt.json | 17 - .../suricata_category_denial_of_service.json | 17 - ...ata_category_denial_of_service_attack.json | 17 - ...category_executable_code_was_detected.json | 17 - ...uricata_category_exploit_kit_activity.json | 17 - ...ategory_external_ip_address_retrieval.json | 17 - .../suricata_category_generic_icmp_event.json | 17 - ...egory_generic_protocol_command_decode.json | 17 - .../suricata_category_information_leak.json | 17 - ...category_large_scale_information_leak.json | 17 - ..._malware_command_and_control_activity.json | 17 - .../suricata_category_misc_activity.json | 17 - .../suricata_category_misc_attack.json | 17 - ...ricata_category_network_scan_detected.json | 17 - ...cata_category_network_trojan_detected.json | 17 - ...ategory_nonstandard_protocol_or_event.json | 17 - ...icata_category_not_suspicious_traffic.json | 17 - .../suricata_category_observed_c2_domain.json | 17 - ...possible_social_engineering_attempted.json | 17 - ...ta_category_possibly_unwanted_program.json | 17 - ...potential_corporate_privacy_violation.json | 17 - ...cata_category_potentially_bad_traffic.json | 17 - ...lly_vulnerable_web_application_access.json | 17 - ...ccessful_administrator_privilege_gain.json | 17 - ..._category_successful_credential_theft.json | 17 - ...tegory_successful_user_privilege_gain.json | 17 - ...category_suspicious_filename_detected.json | 17 - ...uricata_category_system_call_detected.json | 17 - ..._category_targeted_malicious_activity.json | 17 - ...cata_category_tcp_connection_detected.json | 17 - .../suricata_category_unknown_traffic.json | 17 - ...gory_unsuccessful_user_privilege_gain.json | 17 - ...icata_category_web_application_attack.json | 17 - ...baltstrike_artifact_in_an_dns_request.json | 17 - ...a_commonly_abused_dns_domain_detected.json | 17 - ...eversal_characters_in_an_http_request.json | 17 - ...aversal_characters_in_an_http_request.json | 38 -- ...traversal_characters_in_http_response.json | 38 -- ...tory_traversal_in_downloaded_zip_file.json | 38 -- ...icata_dns_traffic_on_unusual_tcp_port.json | 38 -- ...icata_dns_traffic_on_unusual_udp_port.json | 17 - ...ta_double_encoded_characters_in_a_uri.json | 17 - ...le_encoded_characters_in_an_http_post.json | 17 - ...le_encoded_characters_in_http_request.json | 38 -- ..._eval_php_function_in_an_http_request.json | 17 - .../suricata_exploit_cve_2018_1000861.json | 35 -- .../suricata_exploit_cve_2019_0227.json | 35 -- .../suricata_exploit_cve_2019_0232.json | 35 -- .../suricata_exploit_cve_2019_0604.json | 35 -- .../suricata_exploit_cve_2019_0708.json | 35 -- .../suricata_exploit_cve_2019_0752.json | 35 -- .../suricata_exploit_cve_2019_1003000.json | 35 -- .../suricata_exploit_cve_2019_10149.json | 35 -- .../suricata_exploit_cve_2019_11043.json | 35 -- .../suricata_exploit_cve_2019_11510.json | 35 -- .../suricata_exploit_cve_2019_11580.json | 35 -- .../suricata_exploit_cve_2019_11581.json | 35 -- .../suricata_exploit_cve_2019_13450.json | 35 -- .../suricata_exploit_cve_2019_13505.json | 35 -- .../suricata_exploit_cve_2019_15107.json | 35 -- .../suricata_exploit_cve_2019_15846.json | 35 -- .../suricata_exploit_cve_2019_16072.json | 35 -- .../suricata_exploit_cve_2019_1652.json | 35 -- .../suricata_exploit_cve_2019_16662.json | 35 -- .../suricata_exploit_cve_2019_16759.json | 35 -- .../suricata_exploit_cve_2019_16928.json | 35 -- .../suricata_exploit_cve_2019_17270.json | 35 -- .../suricata_exploit_cve_2019_1821.json | 35 -- .../suricata_exploit_cve_2019_19781.json | 35 -- .../suricata_exploit_cve_2019_2618.json | 35 -- .../suricata_exploit_cve_2019_2725.json | 35 -- .../suricata_exploit_cve_2019_3396.json | 35 -- .../suricata_exploit_cve_2019_3929.json | 35 -- .../suricata_exploit_cve_2019_5533.json | 35 -- .../suricata_exploit_cve_2019_6340.json | 35 -- .../suricata_exploit_cve_2019_7256.json | 35 -- .../suricata_exploit_cve_2019_9978.json | 35 -- ..._on_unusual_port_internet_destination.json | 17 - ..._on_unusual_port_internet_destination.json | 17 - ..._on_unusual_port_internet_destination.json | 17 - ...cata_lazagne_artifact_in_an_http_post.json | 17 - ...ta_mimikatz_artifacts_in_an_http_post.json | 17 - ...katz_string_detected_in_http_response.json | 17 - ...uricata_nondns_traffic_on_tcp_port_53.json | 17 - ...uricata_nondns_traffic_on_udp_port_53.json | 17 - .../suricata_nonftp_traffic_on_port_21.json | 17 - ...ricata_nonhttp_traffic_on_tcp_port_80.json | 17 - ...ata_nonimap_traffic_on_port_1443_imap.json | 17 - ...ta_nonsmb_traffic_on_tcp_port_139_smb.json | 17 - .../suricata_nonssh_traffic_on_port_22.json | 17 - .../suricata_nontls_on_tls_port.json | 17 - ...alt_strike_malleable_c2_null_response.json | 17 - ...ion_sql_commands_in_http_transactions.json | 17 - .../suricata_rpc_traffic_on_http_ports.json | 17 - .../suricata_serialized_php_detected.json | 17 - ...ell_exec_php_function_in_an_http_post.json | 17 - ...c_not_on_port_22_internet_destination.json | 17 - ..._on_unusual_port_internet_destination.json | 17 - ...executable_served_by_jpeg_web_content.json | 17 - .../zeek_notice_capturelosstoo_much_loss.json | 17 - .../zeek_notice_conncontent_gap.json | 17 - ...tice_connretransmission_inconsistency.json | 17 - .../zeek_notice_dnsexternal_name.json | 17 - .../zeek_notice_ftpbruteforcing.json | 17 - .../zeek_notice_ftpsite_exec_success.json | 17 - ...notice_heartbleedssl_heartbeat_attack.json | 17 - ...eartbleedssl_heartbeat_attack_success.json | 17 - ...heartbleedssl_heartbeat_many_requests.json | 17 - ...ce_heartbleedssl_heartbeat_odd_length.json | 17 - ...eek_notice_httpsql_injection_attacker.json | 17 - .../zeek_notice_httpsql_injection_victim.json | 17 - .../zeek_notice_intelnotice.json | 17 - .../zeek_notice_noticetally.json | 17 - ...ice_packetfiltercannot_bpf_shunt_conn.json | 17 - ...ek_notice_packetfiltercompile_failure.json | 17 - ...ek_notice_packetfilterdropped_packets.json | 17 - ...ek_notice_packetfilterinstall_failure.json | 17 - ...etfilterno_more_conn_shunts_available.json | 17 - ...acketfiltertoo_long_to_compile_filter.json | 17 - ...notice_protocoldetectorprotocol_found.json | 17 - ...k_notice_protocoldetectorserver_found.json | 17 - .../zeek_notice_scanaddress_scan.json | 17 - .../zeek_notice_scanport_scan.json | 17 - ...zeek_notice_signaturescount_signature.json | 17 - ...ice_signaturesmultiple_sig_responders.json | 17 - ..._notice_signaturesmultiple_signatures.json | 17 - ..._notice_signaturessensitive_signature.json | 17 - ...ek_notice_signaturessignature_summary.json | 17 - ...eek_notice_smtpblocklist_blocked_host.json | 17 - ...ek_notice_smtpblocklist_error_message.json | 17 - ...eek_notice_smtpsuspicious_origination.json | 17 - ...otice_softwaresoftware_version_change.json | 17 - ...eek_notice_softwarevulnerable_version.json | 17 - ..._notice_sshinteresting_hostname_login.json | 17 - ...k_notice_sshlogin_by_password_guesser.json | 17 - .../zeek_notice_sshpassword_guessing.json | 17 - .../zeek_notice_sshwatched_country_login.json | 17 - .../zeek_notice_sslcertificate_expired.json | 17 - ...ek_notice_sslcertificate_expires_soon.json | 17 - ...k_notice_sslcertificate_not_valid_yet.json | 17 - .../zeek_notice_sslinvalid_ocsp_response.json | 17 - .../zeek_notice_sslinvalid_server_cert.json | 17 - .../zeek_notice_sslold_version.json | 17 - .../zeek_notice_sslweak_cipher.json | 17 - .../zeek_notice_sslweak_key.json | 17 - ...ice_teamcymrumalwarehashregistrymatch.json | 17 - .../zeek_notice_traceroutedetected.json | 17 - .../zeek_notice_weirdactivity.json | 17 - 199 files changed, 970 insertions(+), 3972 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json index a65a386cb827e7..e5280d19f8e4a5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -1,16 +1,51 @@ { - "description": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "description": "Adversaries can add the 'hidden' attribute to files to hide them from the user in an attempt to evade detection", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "name": "Adding the Hidden File Attribute with via attrib.exe", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"attrib.exe\" and process.args:\"+h\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "4630d948-40d4-4cef-ac69-4002e29bc3db", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1158", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1158/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1158", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1158/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json index e5d797f3fc1319..0fac9b17160e2a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json @@ -1,16 +1,36 @@ { - "description": "EQL - Adobe Hijack Persistence", + "description": "Detects writing executable files that will be automatically launched by Adobe on launch.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Adobe Hijack Persistence", + "name": "Adobe Hijack Persistence", "query": "file.path:(\"C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\" or \"C:\\Program Files\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\") and event.action:\"File created (rule: FileCreate)\" and not process.name:msiexeec.exe", - "risk_score": 50, + "risk_score": 25, "rule_id": "2bf78aa2-9c56-48de-b139-f169bf99cf86", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1044", + "name": "File System Permissions Weakness", + "reference": "https://attack.mitre.org/techniques/T1044/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json index ef65bd3ecef35d..0506d033489132 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json @@ -1,16 +1,36 @@ { - "description": "EQL - Audio Capture via PowerShell", + "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Audio Capture via PowerShell", + "name": "Audio Capture via PowerShell", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"powershell.exe\" and process.args:\"WindowsAudioDevice-Powershell-Cmdlet\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "b27b9f47-0a20-4807-8377-7f899b4fbada", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "techniques": [ + { + "id": "T1123", + "name": "Audio Capture", + "reference": "https://attack.mitre.org/techniques/T1123/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json index 89eec55d827d6d..392eeb3980c9fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json @@ -1,16 +1,36 @@ { - "description": "EQL - Audio Capture via SoundRecorder", + "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Audio Capture via SoundRecorder", + "name": "Audio Capture via SoundRecorder", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"SoundRecorder.exe\" and process.args:\"/FILE\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "f8e06892-ed10-4452-892e-2c5a38d552f1", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "techniques": [ + { + "id": "T1123", + "name": "Audio Capture", + "reference": "https://attack.mitre.org/techniques/T1123/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json index 80f83991516a67..ecbc9a2dd46c41 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json @@ -1,16 +1,36 @@ { - "description": "EQL -Bypass UAC Event Viewer", + "description": "Identifies User Account Control (UAC) bypass via eventvwr. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL -Bypass UAC Event Viewer", + "name": "Bypass UAC via Event Viewer", "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\System32\\mmc.exe\" or \"C:\\Windows\\SysWOW64\\mmc.exe\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "59547add-a400-4baa-aa0c-66c72efdb77f", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json index 0850632c95899d..2518fda68ee0f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json @@ -1,16 +1,36 @@ { - "description": "EQL - Bypass UAC via CMSTP", + "description": "Identifies User Account Control (UAC) bypass via cmstp. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Bypass UAC via CMSTP", + "name": "Bypass UAC via CMSTP", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"cmstp.exe\" and process.parent.args:(\"/s\" and \"/au\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "2f7403da-1a4c-46bb-8ecc-c1a596e10cd0", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json index 85ba24fd572c37..c419dc080ec3cb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json @@ -1,16 +1,36 @@ { - "description": "EQL -Bypass UAC Via sdclt", + "description": "Identifies User Account Control (UAC) bypass via cmstp. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL -Bypass UAC Via sdclt", + "name": "Bypass UAC via SDCLT", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"sdclt.exe\" and process.args:\"/kickoffelev\" and not process.executable:(\"C:\\Windows\\System32\\sdclt.exe\" or \"C:\\Windows\\System32\\control.exe\" or \"C:\\Windows\\SysWOW64\\sdclt.exe\" or \"C:\\Windows\\SysWOW64\\control.exe\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "f68d83a1-24cb-4b8d-825b-e8af400b9670", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json index 28f45b94049e78..bcf9b02a0210f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -1,16 +1,36 @@ { - "description": "EQL - Clearing Windows Event Logs", + "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt evade detection or destroy forensic evidence on a system.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Clearing Windows Event Logs", + "name": "Clearing Windows Event Logs", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and (process.name:\"wevtutil.exe\" and process.args:\"cl\") or (process.name:\"powershell.exe\" and process.args:\"Clear-EventLog\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "d331bbe2-6db4-4941-80a5-8270db72eb61", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1070", + "name": "Indicator Removal on Host", + "reference": "https://attack.mitre.org/techniques/T1070/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json index 6f00427656af6f..5a9ba605975340 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -1,16 +1,36 @@ { - "description": "EQL - Delete Volume USN Journal with fsutil", + "description": "Identifies use of the fsutil command to delete the volume USNJRNL. This technique is used by attackers to eliminate evidence of files created during post-exploitation activities.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Delete Volume USN Journal with fsutil", + "name": "Delete Volume USN Journal with fsutil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"fsutil.exe\" and process.args:(\"usn\" and \"deletejournal\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "f675872f-6d85-40a3-b502-c0d2ef101e92", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json index 8f5b21b74ee6a3..240678d45238cc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -1,16 +1,36 @@ { - "description": "EQL - Deleting Backup Catalogs with wbadmin", + "description": "Identifies use of the wbadmin command to delete the backup catalog. Ransomware and other malware may do this to prevent system recovery.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Deleting Backup Catalogs with wbadmin", + "name": "Deleting Backup Catalogs with wbadmin", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wbadmin.exe\" and process.args:(\"delete\" and \"catalog\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "581add16-df76-42bb-af8e-c979bfb39a59", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json index 56f0b2efec620c..9e5ccc73dc05ee 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -1,16 +1,36 @@ { - "description": "EQL - Direct Outbound SMB Connection", + "description": "Identifies unexpected processes making network connections over port 445. Windows File Sharing is typically implemented over Server Message Block (SMB), which communicates between hosts using port 445. When legitimate, these network connections are established by the kernel. Processes making 445/tcp connections may be port scanners, exploits, or suspicious user-level processes moving laterally.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Direct Outbound SMB Connection", + "name": "Direct Outbound SMB Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and destination.port:445 and not process.pid:4 and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 50, "rule_id": "c82c7d8f-fb9e-4874-a4bd-fd9e3f9becf1", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "techniques": [ + { + "id": "T1210", + "name": "Exploitation of Remote Services", + "reference": "https://attack.mitre.org/techniques/T1210/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json index 4d1e32eb298978..40a8298561dbd7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -1,16 +1,36 @@ { - "description": "EQL - Disable Windows Firewall Rules with Netsh", + "description": "Identifies use of the netsh command to disable or weaken the local firewall. Attackers will use this command line tool to disable the firewall during troubleshooting or to enable network mobility.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Disable Windows Firewall Rules with Netsh", + "name": "Disable Windows Firewall Rules with Netsh", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"netsh.exe\" and process.args:(\"firewall\" and \"set\" and \"disable\") or process.args:(\"advfirewall\" and \"state\" and \"off\")", "risk_score": 50, "rule_id": "4b438734-3793-4fda-bd42-ceeada0be8f9", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1089", + "name": "Disabling Security Tools", + "reference": "https://attack.mitre.org/techniques/T1089/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json index b9bf463a8e5f22..0ee8674e3304b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json @@ -1,16 +1,51 @@ { - "description": "EQL - DLL Search Order Hijack", + "description": "Detects writing DLL files to known locations associated with Windows files vulnerable to DLL search order hijacking.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - DLL Search Order Hijack", + "name": "DLL Search Order Hijack", "query": " event.action:\"File created (rule: FileCreate)\" and not winlog.user.identifier:(\"S-1-5-18\" or \"S-1-5-19\" or \"S-1-5-20\") and file.path:(\"C\\Windows\\ehome\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptsp.dll\" or \"C\\Windows\\System32\\Sysprep\\rpcrtremote.dll\" or \"C\\Windows\\System32\\Sysprep\\uxtheme.dll\" or \"C\\Windows\\System32\\Sysprep\\dwmapi.dll\" or \"C\\Windows\\System32\\Sysprep\\shcore.dll\" or \"C\\Windows\\System32\\Sysprep\\oleacc.dll\" or \"C\\Windows\\System32\\ntwdblib.dll\") ", "risk_score": 50, "rule_id": "73fbc44c-c3cd-48a8-a473-f4eb2065c716", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json index 6b4ffd9cb21e3b..3e912e076adecf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -1,16 +1,36 @@ { - "description": "EQL - Encoding or Decoding Files via CertUtil", + "description": "Identifies the use of certutil.exe to encode or decode data. CertUtil is a native Windows component which is part of Certificate Services. CertUtil is often abused by attackers to encode or decode base64 data for stealthier command and control or exfiltration.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Encoding or Decoding Files via CertUtil", + "name": "Encoding or Decoding Files via CertUtil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"certutil.exe\" and process.args:(\"-encode\" or \"/encode\" or \"-decode\" or \"/decode\")", "risk_score": 50, "rule_id": "fd70c98a-c410-42dc-a2e3-761c71848acf", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1140", + "name": "Deobfuscate/Decode Files or Information", + "reference": "https://attack.mitre.org/techniques/T1140/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json index f09983d26aff50..304fea1cfbb76c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -1,16 +1,36 @@ { - "description": "EQL - Local Scheduled Task Commands", + "description": "A scheduled task can be used by an adversary to establish persistence, move laterally, and/or escalate privileges.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Local Scheduled Task Commands", + "name": "Local Scheduled Task Commands", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:schtasks.exe and process.args:(\"/create\" or \"-create\" or \"/S\" or \"-s\" or \"/run\" or \"-run\" or \"/change\" or \"-change\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1053", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json index d33a3dbe6de814..7454b0fd452c67 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -1,16 +1,36 @@ { - "description": "EQL - Local Service Commands", + "description": "Identifies use of sc.exe to create, modify, or start services on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Local Service Commands", + "name": "Local Service Commands", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:sc.exe and process.args:(\"create\" or \"config\" or \"failure\" or \"start\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "e8571d5f-bea1-46c2-9f56-998de2d3ed95", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json index 39dc2547520737..d4ac29a78c77de 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json @@ -1,16 +1,36 @@ { - "description": "EQL - Modification of Boot Configuration", + "description": "Identifies use of the bcdedit command to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Modification of Boot Configuration", + "name": "Modification of Boot Configuration", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"bcdedit.exe\" and process.args:\"set\" and process.args:( (\"bootstatuspolicy\" and \"ignoreallfailures\") or (\"recoveryenabled\" and \"no\") ) ", - "risk_score": 50, + "risk_score": 75, "rule_id": "b9ab2f7f-f719-4417-9599-e0252fffe2d8", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json index dd8fab2d8ad706..61049bba92cce4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -1,16 +1,36 @@ { - "description": "EQL - MsBuild Making Network Connections", + "description": "Identifies MsBuild.exe making outbound network connections. This may indicate adversarial activity as MsBuild is often leveraged by adversaries to execute code and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - MsBuild Making Network Connections", + "name": "MsBuild Making Network Connections", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:msbuild.exe and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 50, "rule_id": "0e79980b-4250-4a50-a509-69294c14e84b", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1127", + "name": "Trusted Developer Utilities", + "reference": "https://attack.mitre.org/techniques/T1127/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json index 8037cc9bcba7f0..f2ed8449b9aafa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -1,16 +1,39 @@ { - "description": "EQL - Mshta Making Network Connections", + "description": "Identifies Mshta.exe making outbound network connections. This may indicate adversarial activity as Mshta is often leveraged by adversaries to execute malicious scripts and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Mshta Making Network Connections", + "name": "Mshta Making Network Connections", "query": "event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:\"mshta.exe\" and not process.name:\"mshta.exe\"", + "references": [ + "https://www.fireeye.com/blog/threat-research/2017/05/cyber-espionage-apt32.html" + ], "risk_score": 50, "rule_id": "a4ec1382-4557-452b-89ba-e413b22ed4b8", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1170", + "name": "Mshta", + "reference": "https://attack.mitre.org/techniques/T1170/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json index 5dd6d5d3042c6d..c86b7515173dc4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json @@ -1,16 +1,36 @@ { - "description": "EQL - MsXsl Making Network Connections", + "description": "Identifies MsXsl.exe making outbound network connections. This may indicate adversarial activity as MsXsl is often leveraged by adversaries to execute malicious scripts and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - MsXsl Making Network Connections", + "name": "MsXsl Making Network Connections", "query": "process.name:msxsl.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 50, "rule_id": "d7351b03-135d-43ba-8b36-cc9b07854525", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1220", + "name": "XSL Script Processing", + "reference": "https://attack.mitre.org/techniques/T1220/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json index d83f7796cd4d1a..e35843bc9b4136 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -1,16 +1,19 @@ { - "description": "EQL - PsExec Lateral Movement Command", + "description": "Identifies use of the SysInternals tool PsExec to execute commands on a remote host. This is an indication of lateral movement and may detect adversaries.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - PsExec Lateral Movement Command", + "name": "PsExec Lateral Movement Command", "query": "process.name:psexec.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" ", "risk_score": 50, "rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", "severity": "low", + "tags": [ + "EIA" + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json index 5746541dd879cf..9d3b0361c9d291 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json @@ -1,16 +1,36 @@ { - "description": "EQL - Suspicious MS Office Child Process", + "description": "Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, Excel). These child processes are often launched during exploitation of Office applications or from documents with malicious macros.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Suspicious MS Office Child Process", + "name": "Suspicious MS Office Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"winword.exe\" or \"excel.exe\" or \"powerpnt.exe\" or \"eqnedt32.exe\" or \"fltldr.exe\" or \"mspub.exe\" or \"msaccess.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 25, "rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json index 88ce75eeef34e8..f445cb187c4287 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json @@ -1,16 +1,36 @@ { - "description": "EQL - Suspicious MS Outlook Child Process", + "description": "Identifies suspicious child processes of Microsoft Outlook. These child processes are often associated with spear phishing activity.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Suspicious MS Outlook Child Process", + "name": "Suspicious MS Outlook Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"outlook.exe\" and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 25, "rule_id": "32f4675e-6c49-4ace-80f9-97c9259dca2e", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json index 2e3a654127b53e..0b44ebd922c022 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json @@ -1,5 +1,5 @@ { - "description": "EQL - Suspicious PDF Reader Child Process", + "description": "Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.", "enabled": false, "filters": [], "from": "now-6m", @@ -8,9 +8,29 @@ "language": "kuery", "name": "EQL - Suspicious PDF Reader Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"acrord32.exe\" or \"rdrcef.exe\" or \"foxitphantomPDF.exe\" or \"foxitreader.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 75, "rule_id": "afcac7b1-d092-43ff-a136-aa7accbda38f", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json index 20080719f3ed3b..687f5c0db2dabf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -1,16 +1,36 @@ { - "description": "EQL - System Shells via Services", + "description": "Windows services typically run as SYSTEM and can be used as a privilege escalation opportunity. Malware or penetration testers may run a shell as a service to gain SYSTEM permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - System Shells via Services", + "name": "System Shells via Services", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"services.exe\" and process.name:(\"cmd.exe\" or \"powershell.exe\")", "risk_score": 50, "rule_id": "0022d47d-39c7-4f69-a232-4fe9dc7a3acd", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1050", + "name": "New Service", + "reference": "https://attack.mitre.org/techniques/T1050/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json index 79f8f8e1f606c8..4893f80e8b56c2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Network Connection via RunDLL32", + "description": "Identifies unusual instances of Rundll32.exe making outbound network connections. This may indicate adversarial activity and may identify malicious DLLs.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Network Connection via RunDLL32", + "name": "Unusual Network Connection via RunDLL32", "query": "process.name:rundll32.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, + "risk_score": 25, "rule_id": "52aaab7b-b51c-441a-89ce-4387b3aea886", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1085", + "name": "Rundll32", + "reference": "https://attack.mitre.org/techniques/T1085/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json index 28cce6ed89f8b0..29e3c998ebe02f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Parent-Child Relationship ", + "description": "Identifies Windows programs run from unexpected parent processes. This could indicate masquerading or other strange activity on a system.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Parent-Child Relationship ", + "name": "Unusual Parent-Child Relationship ", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.executable:* and ( (process.name:\"smss.exe\" and not process.parent.name:(\"System\" or \"smss.exe\")) or (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\" or \"svchost.exe\")) or (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"lsass.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"LogonUI.exe\" and not process.parent.name:(\"winlogon.exe\" or \"wininit.exe\")) or (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"svchost.exe\" and not process.parent.name:(\"services.exe\" or \"MsMpEng.exe\")) or (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\" or \"winlogon.exe\")) )", "risk_score": 50, "rule_id": "35df0dd8-092d-4a83-88c1-5151a804f31b", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1093", + "name": "Process Hollowing", + "reference": "https://attack.mitre.org/techniques/T1093/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json index 8b84ec4ff34f48..ce34e4a352c887 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Process Network Connection", + "description": "Identifies network activity from unexpected system applications. This may indicate adversarial activity as these applications are often leveraged by adversaries to execute code and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Process Network Connection", + "name": "Unusual Process Network Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:(bginfo.exe or cdb.exe or cmstp.exe or csi.exe or dnx.exe or fsi.exe or ieexec.exe or iexpress.exe or Microsoft.Workflow.Compiler.exe or odbcconf.exe or rcsi.exe or xwizard.exe)", - "risk_score": 50, + "risk_score": 25, "rule_id": "610949a1-312f-4e04-bb55-3a79b8c95267", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1127", + "name": "Trusted Developer Utilities", + "reference": "https://attack.mitre.org/techniques/T1127/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json index 3af9d9c4277511..5b94babaf8add3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -1,16 +1,36 @@ { - "description": "EQL - User Account Creation", + "description": "Identifies attempts to create new local users. This is sometimes done by attackers to increase access to a system or domain.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - User Account Creation", + "name": "User Account Creation", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"user\" and (\"/add\" or \"/ad\")) ", "risk_score": 50, "rule_id": "1aa9181a-492b-4c01-8b16-fa0735786b2b", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json index 226f2dd1e39342..f0b770985c7166 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json @@ -1,16 +1,36 @@ { - "description": "EQL - User Added to Administrator Group", + "description": "Identifies attempts to add a user to an administrative group with the \"net.exe\" command. This is sometimes done by attackers to increase access of a compromised account or create new account.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - User Added to Administrator Group", + "name": "User Added to Administrator Group", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"group\" and \"admin\" and \"/add\") ", "risk_score": 50, "rule_id": "4426de6f-6103-44aa-a77e-49d672836c27", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json index 2b27bce457aff8..8f23d398a48a74 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -1,16 +1,36 @@ { - "description": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "description": "Identifies use of vssadmin.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "name": "Volume Shadow Copy Deletion via VssAdmin", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"vssadmin.exe\" and process.args:(\"delete\" and \"shadows\") ", - "risk_score": 50, + "risk_score": 75, "rule_id": "b5ea4bfe-a1b2-421f-9d47-22a75a6f2921", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json index 4ec4530cc967f7..fc18b2c0f5d70c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -1,16 +1,36 @@ { - "description": "EQL - Volume Shadow Copy Deletion via WMIC", + "description": "Identifies use of wmic for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Volume Shadow Copy Deletion via WMIC", + "name": "Volume Shadow Copy Deletion via WMIC", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"shadowcopy\" and \"delete\")", - "risk_score": 50, + "risk_score": 75, "rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json index da96eb39e4d96a..ff3d660704eeb7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -1,16 +1,36 @@ { - "description": "EQL - Windows Script Executing PowerShell", + "description": "Identifies a PowerShell process launched by either CScript or WScript. Observing Windows scripting processes executing a PowerShell script, may be indicative of malicious activity.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Windows Script Executing PowerShell", + "name": "Windows Script Executing PowerShell", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"wscript.exe\" or \"cscript.exe\") and process.name:\"powershell.exe\"", "risk_score": 50, "rule_id": "f545ff26-3c94-4fd0-bd33-3c7f95a3a0fc", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json index 3f1c22e2a55d99..5a9bda9e8ddfac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json @@ -1,16 +1,36 @@ { - "description": "EQL - WMIC Command Lateral Movement", + "description": "Identifies use of wmic.exe to run commands on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - WMIC Command Lateral Movement", + "name": "WMIC Command Lateral Movement", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"/node\" or \"-node\") and process.args:(\"call\" or \"set\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "9616587f-6396-42d0-bd31-ef8dbd806210", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "techniques": [ + { + "id": "T1047", + "name": "Windows Management Instrumentation", + "reference": "https://attack.mitre.org/techniques/T1047/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 6ef81addd846ec..a70ff7d13f0eed 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -88,228 +88,68 @@ import rule78 from './network_vnc_virtual_network_computing_from_the_internet.js import rule79 from './network_vnc_virtual_network_computing_to_the_internet.json'; import rule80 from './null_user_agent.json'; import rule81 from './sqlmap_user_agent.json'; -import rule82 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; -import rule83 from './suricata_base64_encoded_newobject_powershell_execution.json'; -import rule84 from './suricata_base64_encoded_startprocess_powershell_execution.json'; -import rule85 from './suricata_category_a_suspicious_string_was_detected.json'; -import rule86 from './suricata_category_attempted_administrator_privilege_gain.json'; -import rule87 from './suricata_category_attempted_denial_of_service.json'; -import rule88 from './suricata_category_attempted_information_leak.json'; -import rule89 from './suricata_category_attempted_login_with_suspicious_username.json'; -import rule90 from './suricata_category_attempted_user_privilege_gain.json'; -import rule91 from './suricata_category_client_using_unusual_port.json'; -import rule92 from './suricata_category_crypto_currency_mining_activity.json'; -import rule93 from './suricata_category_decode_of_an_rpc_query.json'; -import rule94 from './suricata_category_default_username_and_password_login_attempt.json'; -import rule95 from './suricata_category_denial_of_service.json'; -import rule96 from './suricata_category_denial_of_service_attack.json'; -import rule97 from './suricata_category_executable_code_was_detected.json'; -import rule98 from './suricata_category_exploit_kit_activity.json'; -import rule99 from './suricata_category_external_ip_address_retrieval.json'; -import rule100 from './suricata_category_generic_icmp_event.json'; -import rule101 from './suricata_category_generic_protocol_command_decode.json'; -import rule102 from './suricata_category_information_leak.json'; -import rule103 from './suricata_category_large_scale_information_leak.json'; -import rule104 from './suricata_category_malware_command_and_control_activity.json'; -import rule105 from './suricata_category_misc_activity.json'; -import rule106 from './suricata_category_misc_attack.json'; -import rule107 from './suricata_category_network_scan_detected.json'; -import rule108 from './suricata_category_network_trojan_detected.json'; -import rule109 from './suricata_category_nonstandard_protocol_or_event.json'; -import rule110 from './suricata_category_not_suspicious_traffic.json'; -import rule111 from './suricata_category_observed_c2_domain.json'; -import rule112 from './suricata_category_possible_social_engineering_attempted.json'; -import rule113 from './suricata_category_possibly_unwanted_program.json'; -import rule114 from './suricata_category_potential_corporate_privacy_violation.json'; -import rule115 from './suricata_category_potentially_bad_traffic.json'; -import rule116 from './suricata_category_potentially_vulnerable_web_application_access.json'; -import rule117 from './suricata_category_successful_administrator_privilege_gain.json'; -import rule118 from './suricata_category_successful_credential_theft.json'; -import rule119 from './suricata_category_successful_user_privilege_gain.json'; -import rule120 from './suricata_category_suspicious_filename_detected.json'; -import rule121 from './suricata_category_system_call_detected.json'; -import rule122 from './suricata_category_targeted_malicious_activity.json'; -import rule123 from './suricata_category_tcp_connection_detected.json'; -import rule124 from './suricata_category_unknown_traffic.json'; -import rule125 from './suricata_category_unsuccessful_user_privilege_gain.json'; -import rule126 from './suricata_category_web_application_attack.json'; -import rule127 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; -import rule128 from './suricata_commonly_abused_dns_domain_detected.json'; -import rule129 from './suricata_directory_reversal_characters_in_an_http_request.json'; -import rule130 from './suricata_directory_traversal_characters_in_an_http_request.json'; -import rule131 from './suricata_directory_traversal_characters_in_http_response.json'; -import rule132 from './suricata_directory_traversal_in_downloaded_zip_file.json'; -import rule133 from './suricata_dns_traffic_on_unusual_tcp_port.json'; -import rule134 from './suricata_dns_traffic_on_unusual_udp_port.json'; -import rule135 from './suricata_double_encoded_characters_in_a_uri.json'; -import rule136 from './suricata_double_encoded_characters_in_an_http_post.json'; -import rule137 from './suricata_double_encoded_characters_in_http_request.json'; -import rule138 from './suricata_eval_php_function_in_an_http_request.json'; -import rule139 from './suricata_exploit_cve_2018_1000861.json'; -import rule140 from './suricata_exploit_cve_2019_0227.json'; -import rule141 from './suricata_exploit_cve_2019_0232.json'; -import rule142 from './suricata_exploit_cve_2019_0604.json'; -import rule143 from './suricata_exploit_cve_2019_0708.json'; -import rule144 from './suricata_exploit_cve_2019_0752.json'; -import rule145 from './suricata_exploit_cve_2019_1003000.json'; -import rule146 from './suricata_exploit_cve_2019_10149.json'; -import rule147 from './suricata_exploit_cve_2019_11043.json'; -import rule148 from './suricata_exploit_cve_2019_11510.json'; -import rule149 from './suricata_exploit_cve_2019_11580.json'; -import rule150 from './suricata_exploit_cve_2019_11581.json'; -import rule151 from './suricata_exploit_cve_2019_13450.json'; -import rule152 from './suricata_exploit_cve_2019_13505.json'; -import rule153 from './suricata_exploit_cve_2019_15107.json'; -import rule154 from './suricata_exploit_cve_2019_15846.json'; -import rule155 from './suricata_exploit_cve_2019_16072.json'; -import rule156 from './suricata_exploit_cve_2019_1652.json'; -import rule157 from './suricata_exploit_cve_2019_16662.json'; -import rule158 from './suricata_exploit_cve_2019_16759.json'; -import rule159 from './suricata_exploit_cve_2019_16928.json'; -import rule160 from './suricata_exploit_cve_2019_17270.json'; -import rule161 from './suricata_exploit_cve_2019_1821.json'; -import rule162 from './suricata_exploit_cve_2019_19781.json'; -import rule163 from './suricata_exploit_cve_2019_2618.json'; -import rule164 from './suricata_exploit_cve_2019_2725.json'; -import rule165 from './suricata_exploit_cve_2019_3396.json'; -import rule166 from './suricata_exploit_cve_2019_3929.json'; -import rule167 from './suricata_exploit_cve_2019_5533.json'; -import rule168 from './suricata_exploit_cve_2019_6340.json'; -import rule169 from './suricata_exploit_cve_2019_7256.json'; -import rule170 from './suricata_exploit_cve_2019_9978.json'; -import rule171 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; -import rule172 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; -import rule173 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; -import rule174 from './suricata_lazagne_artifact_in_an_http_post.json'; -import rule175 from './suricata_mimikatz_artifacts_in_an_http_post.json'; -import rule176 from './suricata_mimikatz_string_detected_in_http_response.json'; -import rule177 from './suricata_nondns_traffic_on_tcp_port_53.json'; -import rule178 from './suricata_nondns_traffic_on_udp_port_53.json'; -import rule179 from './suricata_nonftp_traffic_on_port_21.json'; -import rule180 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; -import rule181 from './suricata_nonimap_traffic_on_port_1443_imap.json'; -import rule182 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; -import rule183 from './suricata_nonssh_traffic_on_port_22.json'; -import rule184 from './suricata_nontls_on_tls_port.json'; -import rule185 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; -import rule186 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; -import rule187 from './suricata_rpc_traffic_on_http_ports.json'; -import rule188 from './suricata_serialized_php_detected.json'; -import rule189 from './suricata_shell_exec_php_function_in_an_http_post.json'; -import rule190 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; -import rule191 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; -import rule192 from './suricata_windows_executable_served_by_jpeg_web_content.json'; -import rule193 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; -import rule194 from './windows_burp_ce_activity.json'; -import rule195 from './windows_certutil_connecting_to_the_internet.json'; -import rule196 from './windows_command_prompt_connecting_to_the_internet.json'; -import rule197 from './windows_command_shell_started_by_internet_explorer.json'; -import rule198 from './windows_command_shell_started_by_powershell.json'; -import rule199 from './windows_command_shell_started_by_svchost.json'; -import rule200 from './windows_credential_dumping_commands.json'; -import rule201 from './windows_credential_dumping_via_imageload.json'; -import rule202 from './windows_credential_dumping_via_registry_save.json'; -import rule203 from './windows_data_compression_using_powershell.json'; -import rule204 from './windows_defense_evasion_decoding_using_certutil.json'; -import rule205 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; -import rule206 from './windows_defense_evasion_via_filter_manager.json'; -import rule207 from './windows_defense_evasion_via_windows_event_log_tools.json'; -import rule208 from './windows_execution_via_compiled_html_file.json'; -import rule209 from './windows_execution_via_connection_manager.json'; -import rule210 from './windows_execution_via_microsoft_html_application_hta.json'; -import rule211 from './windows_execution_via_net_com_assemblies.json'; -import rule212 from './windows_execution_via_regsvr32.json'; -import rule213 from './windows_execution_via_trusted_developer_utilities.json'; -import rule214 from './windows_html_help_executable_program_connecting_to_the_internet.json'; -import rule215 from './windows_image_load_from_a_temp_directory.json'; -import rule216 from './windows_indirect_command_execution.json'; -import rule217 from './windows_iodine_activity.json'; -import rule218 from './windows_management_instrumentation_wmi_execution.json'; -import rule219 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; -import rule220 from './windows_mimikatz_activity.json'; -import rule221 from './windows_misc_lolbin_connecting_to_the_internet.json'; -import rule222 from './windows_net_command_activity_by_the_system_account.json'; -import rule223 from './windows_net_user_command_activity.json'; -import rule224 from './windows_netcat_activity.json'; -import rule225 from './windows_netcat_network_activity.json'; -import rule226 from './windows_network_anomalous_windows_process_using_https_ports.json'; -import rule227 from './windows_nmap_activity.json'; -import rule228 from './windows_nmap_scan_activity.json'; -import rule229 from './windows_payload_obfuscation_via_certutil.json'; -import rule230 from './windows_persistence_or_priv_escalation_via_hooking.json'; -import rule231 from './windows_persistence_via_application_shimming.json'; -import rule232 from './windows_persistence_via_bits_jobs.json'; -import rule233 from './windows_persistence_via_modification_of_existing_service.json'; -import rule234 from './windows_persistence_via_netshell_helper_dll.json'; -import rule235 from './windows_powershell_connecting_to_the_internet.json'; -import rule236 from './windows_priv_escalation_via_accessibility_features.json'; -import rule237 from './windows_process_discovery_via_tasklist_command.json'; -import rule238 from './windows_process_execution_via_wmi.json'; -import rule239 from './windows_process_started_by_acrobat_reader_possible_payload.json'; -import rule240 from './windows_process_started_by_ms_office_program_possible_payload.json'; -import rule241 from './windows_process_started_by_the_java_runtime.json'; -import rule242 from './windows_psexec_activity.json'; -import rule243 from './windows_register_server_program_connecting_to_the_internet.json'; -import rule244 from './windows_registry_query_local.json'; -import rule245 from './windows_registry_query_network.json'; -import rule246 from './windows_remote_management_execution.json'; -import rule247 from './windows_scheduled_task_activity.json'; -import rule248 from './windows_script_interpreter_connecting_to_the_internet.json'; -import rule249 from './windows_signed_binary_proxy_execution.json'; -import rule250 from './windows_signed_binary_proxy_execution_download.json'; -import rule251 from './windows_suspicious_process_started_by_a_script.json'; -import rule252 from './windows_whoami_command_activity.json'; -import rule253 from './windows_windump_activity.json'; -import rule254 from './windows_wireshark_activity.json'; -import rule255 from './zeek_notice_capturelosstoo_much_loss.json'; -import rule256 from './zeek_notice_conncontent_gap.json'; -import rule257 from './zeek_notice_connretransmission_inconsistency.json'; -import rule258 from './zeek_notice_dnsexternal_name.json'; -import rule259 from './zeek_notice_ftpbruteforcing.json'; -import rule260 from './zeek_notice_ftpsite_exec_success.json'; -import rule261 from './zeek_notice_heartbleedssl_heartbeat_attack.json'; -import rule262 from './zeek_notice_heartbleedssl_heartbeat_attack_success.json'; -import rule263 from './zeek_notice_heartbleedssl_heartbeat_many_requests.json'; -import rule264 from './zeek_notice_heartbleedssl_heartbeat_odd_length.json'; -import rule265 from './zeek_notice_httpsql_injection_attacker.json'; -import rule266 from './zeek_notice_httpsql_injection_victim.json'; -import rule267 from './zeek_notice_intelnotice.json'; -import rule268 from './zeek_notice_noticetally.json'; -import rule269 from './zeek_notice_packetfiltercannot_bpf_shunt_conn.json'; -import rule270 from './zeek_notice_packetfiltercompile_failure.json'; -import rule271 from './zeek_notice_packetfilterdropped_packets.json'; -import rule272 from './zeek_notice_packetfilterinstall_failure.json'; -import rule273 from './zeek_notice_packetfilterno_more_conn_shunts_available.json'; -import rule274 from './zeek_notice_packetfiltertoo_long_to_compile_filter.json'; -import rule275 from './zeek_notice_protocoldetectorprotocol_found.json'; -import rule276 from './zeek_notice_protocoldetectorserver_found.json'; -import rule277 from './zeek_notice_scanaddress_scan.json'; -import rule278 from './zeek_notice_scanport_scan.json'; -import rule279 from './zeek_notice_signaturescount_signature.json'; -import rule280 from './zeek_notice_signaturesmultiple_sig_responders.json'; -import rule281 from './zeek_notice_signaturesmultiple_signatures.json'; -import rule282 from './zeek_notice_signaturessensitive_signature.json'; -import rule283 from './zeek_notice_signaturessignature_summary.json'; -import rule284 from './zeek_notice_smtpblocklist_blocked_host.json'; -import rule285 from './zeek_notice_smtpblocklist_error_message.json'; -import rule286 from './zeek_notice_smtpsuspicious_origination.json'; -import rule287 from './zeek_notice_softwaresoftware_version_change.json'; -import rule288 from './zeek_notice_softwarevulnerable_version.json'; -import rule289 from './zeek_notice_sshinteresting_hostname_login.json'; -import rule290 from './zeek_notice_sshlogin_by_password_guesser.json'; -import rule291 from './zeek_notice_sshpassword_guessing.json'; -import rule292 from './zeek_notice_sshwatched_country_login.json'; -import rule293 from './zeek_notice_sslcertificate_expired.json'; -import rule294 from './zeek_notice_sslcertificate_expires_soon.json'; -import rule295 from './zeek_notice_sslcertificate_not_valid_yet.json'; -import rule296 from './zeek_notice_sslinvalid_ocsp_response.json'; -import rule297 from './zeek_notice_sslinvalid_server_cert.json'; -import rule298 from './zeek_notice_sslold_version.json'; -import rule299 from './zeek_notice_sslweak_cipher.json'; -import rule300 from './zeek_notice_sslweak_key.json'; -import rule301 from './zeek_notice_teamcymrumalwarehashregistrymatch.json'; -import rule302 from './zeek_notice_traceroutedetected.json'; -import rule303 from './zeek_notice_weirdactivity.json'; +import rule82 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; +import rule83 from './windows_burp_ce_activity.json'; +import rule84 from './windows_certutil_connecting_to_the_internet.json'; +import rule85 from './windows_command_prompt_connecting_to_the_internet.json'; +import rule86 from './windows_command_shell_started_by_internet_explorer.json'; +import rule87 from './windows_command_shell_started_by_powershell.json'; +import rule88 from './windows_command_shell_started_by_svchost.json'; +import rule89 from './windows_credential_dumping_commands.json'; +import rule90 from './windows_credential_dumping_via_imageload.json'; +import rule91 from './windows_credential_dumping_via_registry_save.json'; +import rule92 from './windows_data_compression_using_powershell.json'; +import rule93 from './windows_defense_evasion_decoding_using_certutil.json'; +import rule94 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; +import rule95 from './windows_defense_evasion_via_filter_manager.json'; +import rule96 from './windows_defense_evasion_via_windows_event_log_tools.json'; +import rule97 from './windows_execution_via_compiled_html_file.json'; +import rule98 from './windows_execution_via_connection_manager.json'; +import rule99 from './windows_execution_via_microsoft_html_application_hta.json'; +import rule100 from './windows_execution_via_net_com_assemblies.json'; +import rule101 from './windows_execution_via_regsvr32.json'; +import rule102 from './windows_execution_via_trusted_developer_utilities.json'; +import rule103 from './windows_html_help_executable_program_connecting_to_the_internet.json'; +import rule104 from './windows_image_load_from_a_temp_directory.json'; +import rule105 from './windows_indirect_command_execution.json'; +import rule106 from './windows_iodine_activity.json'; +import rule107 from './windows_management_instrumentation_wmi_execution.json'; +import rule108 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; +import rule109 from './windows_mimikatz_activity.json'; +import rule110 from './windows_misc_lolbin_connecting_to_the_internet.json'; +import rule111 from './windows_net_command_activity_by_the_system_account.json'; +import rule112 from './windows_net_user_command_activity.json'; +import rule113 from './windows_netcat_activity.json'; +import rule114 from './windows_netcat_network_activity.json'; +import rule115 from './windows_network_anomalous_windows_process_using_https_ports.json'; +import rule116 from './windows_nmap_activity.json'; +import rule117 from './windows_nmap_scan_activity.json'; +import rule118 from './windows_payload_obfuscation_via_certutil.json'; +import rule119 from './windows_persistence_or_priv_escalation_via_hooking.json'; +import rule120 from './windows_persistence_via_application_shimming.json'; +import rule121 from './windows_persistence_via_bits_jobs.json'; +import rule122 from './windows_persistence_via_modification_of_existing_service.json'; +import rule123 from './windows_persistence_via_netshell_helper_dll.json'; +import rule124 from './windows_powershell_connecting_to_the_internet.json'; +import rule125 from './windows_priv_escalation_via_accessibility_features.json'; +import rule126 from './windows_process_discovery_via_tasklist_command.json'; +import rule127 from './windows_process_execution_via_wmi.json'; +import rule128 from './windows_process_started_by_acrobat_reader_possible_payload.json'; +import rule129 from './windows_process_started_by_ms_office_program_possible_payload.json'; +import rule130 from './windows_process_started_by_the_java_runtime.json'; +import rule131 from './windows_psexec_activity.json'; +import rule132 from './windows_register_server_program_connecting_to_the_internet.json'; +import rule133 from './windows_registry_query_local.json'; +import rule134 from './windows_registry_query_network.json'; +import rule135 from './windows_remote_management_execution.json'; +import rule136 from './windows_scheduled_task_activity.json'; +import rule137 from './windows_script_interpreter_connecting_to_the_internet.json'; +import rule138 from './windows_signed_binary_proxy_execution.json'; +import rule139 from './windows_signed_binary_proxy_execution_download.json'; +import rule140 from './windows_suspicious_process_started_by_a_script.json'; +import rule141 from './windows_whoami_command_activity.json'; +import rule142 from './windows_windump_activity.json'; +import rule143 from './windows_wireshark_activity.json'; export const rawRules = [ rule1, rule2, @@ -454,164 +294,4 @@ export const rawRules = [ rule141, rule142, rule143, - rule144, - rule145, - rule146, - rule147, - rule148, - rule149, - rule150, - rule151, - rule152, - rule153, - rule154, - rule155, - rule156, - rule157, - rule158, - rule159, - rule160, - rule161, - rule162, - rule163, - rule164, - rule165, - rule166, - rule167, - rule168, - rule169, - rule170, - rule171, - rule172, - rule173, - rule174, - rule175, - rule176, - rule177, - rule178, - rule179, - rule180, - rule181, - rule182, - rule183, - rule184, - rule185, - rule186, - rule187, - rule188, - rule189, - rule190, - rule191, - rule192, - rule193, - rule194, - rule195, - rule196, - rule197, - rule198, - rule199, - rule200, - rule201, - rule202, - rule203, - rule204, - rule205, - rule206, - rule207, - rule208, - rule209, - rule210, - rule211, - rule212, - rule213, - rule214, - rule215, - rule216, - rule217, - rule218, - rule219, - rule220, - rule221, - rule222, - rule223, - rule224, - rule225, - rule226, - rule227, - rule228, - rule229, - rule230, - rule231, - rule232, - rule233, - rule234, - rule235, - rule236, - rule237, - rule238, - rule239, - rule240, - rule241, - rule242, - rule243, - rule244, - rule245, - rule246, - rule247, - rule248, - rule249, - rule250, - rule251, - rule252, - rule253, - rule254, - rule255, - rule256, - rule257, - rule258, - rule259, - rule260, - rule261, - rule262, - rule263, - rule264, - rule265, - rule266, - rule267, - rule268, - rule269, - rule270, - rule271, - rule272, - rule273, - rule274, - rule275, - rule276, - rule277, - rule278, - rule279, - rule280, - rule281, - rule282, - rule283, - rule284, - rule285, - rule286, - rule287, - rule288, - rule289, - rule290, - rule291, - rule292, - rule293, - rule294, - rule295, - rule296, - rule297, - rule298, - rule299, - rule300, - rule301, - rule302, - rule303, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json index 90864f1ab8ab9f..d6887f7928dd80 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json @@ -1,15 +1,46 @@ { - "description": "Linux: Kernel Module Activity", + "description": "Identifies loadable kernel module errors, often indicative of potential persistence attempts.", "enabled": false, + "false_positives": [ + "Security tools and device drivers may load legitimate kernel modules." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Kernel Module Activity", + "max_signals": 33, + "name": "Persistence via Kernel Module Modification", "query": "process.name: (insmod or kmod or modprobe or rmod) and event.action:executed", - "risk_score": 50, + "references": [ + "https://www.hackers-arise.com/single-post/2017/11/03/Linux-for-Hackers-Part-10-Loadable-Kernel-Modules-LKM" + ], + "risk_score": 25, "rule_id": "81cc58f5-8062-49a2-ba84-5cc4b4d31c40", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/techniques/TA0003/" + }, + "techniques": [ + { + "id": "T1215", + "name": "Kernel Modules and Extensions", + "reference": "https://attack.mitre.org/techniques/T1215/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index d9d409feae4735..945c8acfe00e45 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -1,15 +1,26 @@ { - "description": "Linux: Process Started in Temp Directory", + "description": "Identifies processes running in a temporary folder. This is sometimes done by adversaries to hide malware.", "enabled": false, + "false_positives": [ + "Build systems like Jenkins may start processes in the /tmp directory." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Process Started in Temp Directory", + "max_signals": 33, + "name": "Unusual Process Execution - Temp", "query": "process.working_directory: /tmp and event.action:executed", - "risk_score": 50, + "risk_score": 25, "rule_id": "df959768-b0c9-4d45-988c-5606a2be8e5a", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json index d533f5d4ec3f64..e8c5942ec5100f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json @@ -1,16 +1,47 @@ { - "description": "Linux: Shell Activity By Web Server", + "description": "Identifies suspicious commands executed via a web server, which may suggest a vulnerability and remote shell access.", "enabled": false, + "false_positives": [ + "Network monitoring or management products may have a web server component that runs shell commands as part of normal behavior." + ], "filters": [], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", + "max_signals": 33, "name": "Linux: Shell Activity By Web Server", "query": "process.name: bash and (user.name: apache or www) and event.action:executed", + "references": [ + "https://pentestlab.blog/tag/web-shell/" + ], "risk_score": 50, "rule_id": "231876e7-4d1f-4d63-a47c-47dd1acdc1cb", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/techniques/TA0003/" + }, + "techniques": [ + { + "id": "T1100", + "name": "Web Shell", + "reference": "https://attack.mitre.org/techniques/T1215/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json index 56a2782eb0cca0..c57e21334b4f76 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json @@ -1,15 +1,43 @@ { - "description": "Linux: Whoami Commmand", + "description": "The 'whoami' command was executed on a Linux host. This is often used by tools and persistence mechanisms to test for privlieged access.", "enabled": false, + "false_positives": [ + "Security testing tools and frameworks may run this command. Some normal use of this command may originate from automation tools and frameworks." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Whoami Commmand", + "max_signals": 33, + "name": "Linux: User Discovery Via The Whoami Commmand", "query": "process.name: whoami and event.action:executed", "risk_score": 50, "rule_id": "120559c6-5e24-49f4-9e30-8ffe697df6b9", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "techniques": [ + { + "id": "T1033", + "name": "System Owner/User Discovery", + "reference": "https://attack.mitre.org/techniques/T1033/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json deleted file mode 100644 index 05d54f6bdb4c63..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded Invoke-Command Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded Invoke-Command Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610182 or 2610183 or 2610184 or 2610185 or 2610186 or 2610187) or rule.id: (2610182 or 2610183 or 2610184 or 2610185 or 2610186 or 2610187))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L179-L184", - "This group of signatures detect base-64 encoded variations of the 'Invoke-Command' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "6ff01a30-95dd-471c-b61d-0fd9ee2d0a20", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json deleted file mode 100644 index ac47a6877c5250..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded New-Object Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded New-Object Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610188 or 2610189 or 2610190 or 2610191 or 2610192 or 2610193) or rule.id: (2610188 or 2610189 or 2610190 or 2610191 or 2610192 or 2610193))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L191-L196", - "This group of signatures detect base-64 encoded variations of the 'New-Object' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "d14d5401-0f7a-4933-b816-1b8f823e3d84", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json deleted file mode 100644 index 972299bbd74b04..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded Start-Process Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded Start-Process Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610194 or 2610195 or 2610196 or 2610197 or 2610198 or 2610199) or rule.id: (2610194 or 2610195 or 2610196 or 2610197 or 2610198 or 2610199))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L191-L196", - "This group of signatures detect base-64 encoded variations of the 'Start-Process' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "372dce88-003d-4bcf-8c95-34ea8be180a1", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json deleted file mode 100644 index bb6a57f905bf7d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - A suspicious string was detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - A suspicious string was detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious string was detected\" or rule.category: \"A suspicious string was detected\")", - "risk_score": 50, - "rule_id": "2a3d91c1-5065-46ab-bed0-93f80835b1d5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json deleted file mode 100644 index 9de1f5ad33712e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Administrator Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Administrator Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Administrator Privilege Gain\" or rule.category: \"Attempted Administrator Privilege Gain\")", - "risk_score": 50, - "rule_id": "f840129e-9089-4f46-8af1-0745e8f54713", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json deleted file mode 100644 index d0c3eb9ba2331c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Denial of Service", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Denial of Service", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Denial of Service\" or rule.category: \"Attempted Denial of Service\")", - "risk_score": 50, - "rule_id": "a62927f4-2488-4679-b56f-cda1a7f4c9e1", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json deleted file mode 100644 index 75995d657b4640..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Information Leak\" or rule.category: \"Attempted Information Leak\")", - "risk_score": 50, - "rule_id": "88d69362-f496-41d6-8e6b-a2dbaed3513f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json deleted file mode 100644 index 31d14a3b687089..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Login with Suspicious Username", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Login with Suspicious Username", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"An attempted login using a suspicious username was detected\" or rule.category: \"An attempted login using a suspicious username was detected\")", - "risk_score": 50, - "rule_id": "a84cd36c-dd5a-4e86-a2ce-44556c21cef0", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json deleted file mode 100644 index 13300e8a17694d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted User Privilege Gain\" or rule.category: \"Attempted User Privilege Gain\")", - "risk_score": 50, - "rule_id": "eabce895-4602-4d20-8bf9-11c903bb3e08", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json deleted file mode 100644 index 9c1e3ef1b39f8e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Client Using Unusual Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Client Using Unusual Port", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A client was using an unusual port\" or rule.category: \"A client was using an unusual port\")", - "risk_score": 50, - "rule_id": "00503a3c-304c-421c-bfea-e5d8fdfd9726", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json deleted file mode 100644 index a4ef732c2e1bd5..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Crypto Currency Mining Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Crypto Currency Mining Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Crypto Currency Mining Activity Detected\" or rule.category: \"Crypto Currency Mining Activity Detected\")", - "risk_score": 50, - "rule_id": "74cd4920-a441-41d2-8a23-5bee70626e60", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json deleted file mode 100644 index 43f767f14b7e6c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Decode of an RPC Query", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Decode of an RPC Query", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Decode of an RPC Query\" or rule.category: \"Decode of an RPC Query\")", - "risk_score": 50, - "rule_id": "e9fc5bd3-c8a1-442c-be6d-032da07c508b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json deleted file mode 100644 index 74a566563f15a3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Default Username and Password Login Attempt", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Default Username and Password Login Attempt", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempt to login by a default username and password\" or rule.category: \"Attempt to login by a default username and password\")", - "risk_score": 50, - "rule_id": "190bd112-f831-4813-98b2-e45a934277c2", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json deleted file mode 100644 index d7a615807593e6..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Denial of Service", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Denial of Service", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Denial of Service\" or rule.category: \"Denial of Service\")", - "risk_score": 75, - "rule_id": "0e97e390-84db-4725-965a-a8b0b600f7be", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json deleted file mode 100644 index e0bf4220d4467f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Denial of Service Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Denial of Service Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Denial of Service Attack\" or rule.category: \"Detection of a Denial of Service Attack\")", - "risk_score": 100, - "rule_id": "42a60eaa-fd20-479b-b6ca-bdb88d47b34b", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json deleted file mode 100644 index 09a72e761cb409..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Executable code was detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Executable code was detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Executable code was detected\" or rule.category: \"Executable code was detected\")", - "risk_score": 50, - "rule_id": "4699296b-5127-475a-9d83-8434fcd18136", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json deleted file mode 100644 index 8c8f5565da4e64..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Exploit Kit Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Exploit Kit Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Exploit Kit Activity Detected\" or rule.category: \"Exploit Kit Activity Detected\")", - "risk_score": 50, - "rule_id": "b3111af8-79bf-4ec3-97ae-28d9ed9fbd38", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json deleted file mode 100644 index 39c42d81ee59d5..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - External IP Address Retrieval", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - External IP Address Retrieval", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Device Retrieving External IP Address Detected\" or rule.category: \"Device Retrieving External IP Address Detected\")", - "risk_score": 50, - "rule_id": "c7df9ecf-d6be-4ef8-9871-cb317dfff0b4", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json deleted file mode 100644 index e4d15f667371f9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Generic ICMP event", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Generic ICMP event", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic ICMP event\" or rule.category: \"Generic ICMP event\")", - "risk_score": 25, - "rule_id": "3309bffa-7c43-409a-acea-6631c1b077e5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json deleted file mode 100644 index faaccc5eee9926..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Generic Protocol Command Decode", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Generic Protocol Command Decode", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic Protocol Command Decode\" or rule.category: \"Generic Protocol Command Decode\")", - "risk_score": 25, - "rule_id": "6fd2deb4-a7a9-4221-8b7b-8d26836a8c30", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json deleted file mode 100644 index c58b4a5f4b13a3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Information Leak\" or rule.category: \"Information Leak\")", - "risk_score": 25, - "rule_id": "95df8ff4-7169-4c84-ae50-3561b1d1bc91", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json deleted file mode 100644 index b1916165c6e903..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Large Scale Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Large Scale Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Large Scale Information Leak\" or rule.category: \"Large Scale Information Leak\")", - "risk_score": 75, - "rule_id": "ca98de30-c703-4170-97ae-ab2b340f6080", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json deleted file mode 100644 index 4682f973bdfc93..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Malware Command and Control Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Malware Command and Control Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Malware Command and Control Activity Detected\" or rule.category: \"Malware Command and Control Activity Detected\")", - "risk_score": 100, - "rule_id": "56656341-2940-4a69-b8fe-acf3c734f540", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json deleted file mode 100644 index 49928bd4caaa53..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Misc Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Misc Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc activity\" or rule.category: \"Misc activity\")", - "risk_score": 25, - "rule_id": "403ddbde-a486-4dd7-b932-cee4ebef88b6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json deleted file mode 100644 index 34c9059d264981..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Misc Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Misc Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc Attack\" or rule.category: \"Misc Attack\")", - "risk_score": 50, - "rule_id": "83277123-749f-49da-ad3d-d59f35490db1", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json deleted file mode 100644 index 9bc0572e257795..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Network Scan Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Network Scan Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Network Scan\" or rule.category: \"Detection of a Network Scan\")", - "risk_score": 25, - "rule_id": "7e969b45-d005-4173-aee7-a7aaa79bc372", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json deleted file mode 100644 index b319d5d2be079b..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Network Trojan Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Network Trojan Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A Network Trojan was detected\" or rule.category: \"A Network Trojan was detected\")", - "risk_score": 100, - "rule_id": "76ffa464-ec03-42e1-87ee-87760c331061", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json deleted file mode 100644 index c104b1d2acc450..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Non-Standard Protocol or Event", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Non-Standard Protocol or Event", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a non-standard protocol or event\" or rule.category: \"Detection of a non-standard protocol or event\")", - "risk_score": 50, - "rule_id": "82f9f485-873b-4eeb-b231-052ab81e05b8", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json deleted file mode 100644 index 4ff46e429c4c3a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Not Suspicious Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Not Suspicious Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Not Suspicious Traffic\" or rule.category: \"Not Suspicious Traffic\")", - "risk_score": 25, - "rule_id": "c0f684ff-4f15-44e7-912d-aa8b8f08a910", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json deleted file mode 100644 index 6b06e23648cbdb..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Observed C2 Domain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Observed C2 Domain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Domain Observed Used for C2 Detected\" or rule.category: \"Domain Observed Used for C2 Detected\")", - "risk_score": 75, - "rule_id": "8adfa89f-aa90-4d26-9d7a-7da652cae902", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json deleted file mode 100644 index 7c4f096280ed47..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Possible Social Engineering Attempted", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Possible Social Engineering Attempted", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possible Social Engineering Attempted\" or rule.category: \"Possible Social Engineering Attempted\")", - "risk_score": 50, - "rule_id": "7d2d5a5f-f590-407d-933a-42adb1a7bcef", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json deleted file mode 100644 index 7e5f92c15e4141..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Possibly Unwanted Program", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Possibly Unwanted Program", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possibly Unwanted Program Detected\" or rule.category: \"Possibly Unwanted Program Detected\")", - "risk_score": 25, - "rule_id": "1b9a31e8-fdfa-400e-aa4e-79a7f1a1da18", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json deleted file mode 100644 index 221cfaab48e004..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potential Corporate Privacy Violation", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potential Corporate Privacy Violation", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potential Corporate Privacy Violation\" or rule.category: \"Potential Corporate Privacy Violation\")", - "risk_score": 25, - "rule_id": "1c70f5d5-eae0-4d00-b35a-d34ca607094e", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json deleted file mode 100644 index fc1baf20147577..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potentially Bad Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potentially Bad Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potentially Bad Traffic\" or rule.category: \"Potentially Bad Traffic\")", - "risk_score": 25, - "rule_id": "197cdd5a-9880-4780-a87c-594d0ed2b7b4", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json deleted file mode 100644 index cfcb246d44f4d1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potentially Vulnerable Web Application Access", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potentially Vulnerable Web Application Access", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"access to a potentially vulnerable web application\" or rule.category: \"access to a potentially vulnerable web application\")", - "risk_score": 75, - "rule_id": "0993e926-1a01-4c28-918a-cdd5741a19a8", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json deleted file mode 100644 index 919083650682c9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful Administrator Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful Administrator Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Administrator Privilege Gain\" or rule.category: \"Successful Administrator Privilege Gain\")", - "risk_score": 75, - "rule_id": "f068e655-1f52-4d81-839a-9c08c6543ceb", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json deleted file mode 100644 index feb708316fbd8a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful Credential Theft", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful Credential Theft", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Credential Theft Detected\" or rule.category: \"Successful Credential Theft Detected\")", - "risk_score": 75, - "rule_id": "90f3e735-2187-4e8e-8d28-6e3249964851", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json deleted file mode 100644 index 8a7e366d25e585..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful User Privilege Gain\" or rule.category: \"Successful User Privilege Gain\")", - "risk_score": 50, - "rule_id": "f8ebd022-6e92-4b80-ac49-7ee011ba2ce0", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json deleted file mode 100644 index 356c0d23dd4e9c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Suspicious Filename Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Suspicious Filename Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious filename was detected\" or rule.category: \"A suspicious filename was detected\")", - "risk_score": 25, - "rule_id": "d0489b07-8140-4e3d-a2b7-52f2c06fdc7c", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json deleted file mode 100644 index f41692fb218412..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - System Call Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - System Call Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A system call was detected\" or rule.category: \"A system call was detected\")", - "risk_score": 50, - "rule_id": "44a5c55a-a34f-43c3-8f21-df502862aa9b", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json deleted file mode 100644 index 9c13b53f43263d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Targeted Malicious Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Targeted Malicious Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Targeted Malicious Activity was Detected\" or rule.category: \"Targeted Malicious Activity was Detected\")", - "risk_score": 75, - "rule_id": "d299379d-41de-4640-96b6-77aaa9adfa6f", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json deleted file mode 100644 index eb41269d58ffa1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - TCP Connection Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - TCP Connection Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A TCP connection was detected\" or rule.category: \"A TCP connection was detected\")", - "risk_score": 0, - "rule_id": "ddf402cf-307d-4f46-a25d-dce3aee1ad13", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json deleted file mode 100644 index a260d049633b98..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Unknown Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Unknown Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unknown Traffic\" or rule.category: \"Unknown Traffic\")", - "risk_score": 25, - "rule_id": "827ea90c-00c2-45f7-b873-dd060297b2d2", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json deleted file mode 100644 index c57cc857cef676..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Unsuccessful User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Unsuccessful User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unsuccessful User Privilege Gain\" or rule.category: \"Unsuccessful User Privilege Gain\")", - "risk_score": 50, - "rule_id": "85471d30-78c9-48f6-b2db-ab5b2547e450", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json deleted file mode 100644 index 4014473971b8ef..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Web Application Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Web Application Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Web Application Attack\" or rule.category: \"Web Application Attack\")", - "risk_score": 75, - "rule_id": "e856918b-f26e-4893-84b9-3deb65046fb7", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json deleted file mode 100644 index e77e977d780d5e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata CobaltStrike Artifact in an DNS Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata CobaltStrike Artifact in an DNS Request", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610166 or 2610167 or 2610168) or rule.id: (2610166 or 2610167 or 2610168))", - "risk_score": 100, - "rule_id": "481ef0f5-beda-4fa2-8bfb-039c95500deb", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json deleted file mode 100644 index a866c79a858224..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Commonly Abused DNS Domain Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Commonly Abused DNS Domain Detected", - "query": " event.module:suricata and event.kind:alert and (suricata.eve.alert.signature:(TGI* and *HUNT* and *Abused* and *TLD*) or rule.description:(TGI* and *HUNT* and *Abused* and *TLD*))", - "risk_score": 25, - "rule_id": "1844dfe1-b05e-4ca6-b367-6b9e3a1fe227", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json deleted file mode 100644 index 862d5417fadcc4..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Directory Reversal Characters in an HTTP Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Reversal Characters in an HTTP Request", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610161 or 2610162)", - "risk_score": 50, - "rule_id": "c0ca8090-60f8-4458-befe-c43687b648a3", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json deleted file mode 100644 index 73cb913e271a16..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal Characters in an HTTP Request Header", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal Characters in an HTTP Request Header", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610161 or 2610162) or rule.id: (2610161 or 2610162))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L179-L184", - "This group of signatures detects directory traversal characters in a header of an HTTP request. This is not something you should see on a typical network and could indicate an attempt to exploit the web application." - ], - "risk_score": 50, - "rule_id": "7c663c8d-cdfd-4605-9dd6-d682fa4ade8c", - "severity": "medium", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json deleted file mode 100644 index c9d0db8ed300ee..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal Characters in HTTP Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal Characters in HTTP Response", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610086 or rule.id:2610086)", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L89", - "This group of signatures detects directory traversal characters in a header of an HTTP response. This is not something you should see on a typical network and could indicate an attempt to exploit the web application." - ], - "risk_score": 75, - "rule_id": "a6406974-ea70-45b5-b5d8-ca17695adbde", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json deleted file mode 100644 index 65f8195751fc52..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal in Downloaded Zip File", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal in Downloaded Zip File", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610085 or rule.id:2610085)", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L88", - "This group of signatures detects directory traversal characters in a zip archive downloaded over the network. This is not something you should see on a typical network and could indicate an attempt to trick a user to overwrite system files." - ], - "risk_score": 75, - "rule_id": "d5d990bc-303c-4241-8138-6ba3cf2ee93e", - "severity": "medium", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "techniques": [ - { - "id": "T1204", - "name": "user execution", - "reference": "https://attack.mitre.org/techniques/T1204/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json deleted file mode 100644 index bd73b822f9f495..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata DNS Traffic on Unusual Port (TCP or UDP)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata DNS Traffic on Unusual Port", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610015 or 2610013) or rule.id: (2610015 or 2610013))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L14-L16", - "This detects DNS traffic running on an unusual port. This could indicate an application that is misconfigured or attempting to bypass security controls." - ], - "risk_score": 50, - "rule_id": "deeae336-4ff7-4cf8-ae5b-18bce05da02e", - "severity": "low", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1065", - "name": "uncommonly used port", - "reference": "https://attack.mitre.org/techniques/T1065/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json deleted file mode 100644 index eb9b06f3cab145..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata DNS Traffic on Unusual UDP Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata DNS Traffic on Unusual UDP Port", - "query": "suricata.eve.alert.signature_id:2610015 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "2343d9a4-365b-45b2-acb0-76934d43c75b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json deleted file mode 100644 index eaed3aabed8f24..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in a URI", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in a URI", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610092 or 2610093 or 2610094 or 2610095)", - "risk_score": 50, - "rule_id": "1ed4d2d1-330c-4c7d-b32d-2d8805437946", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json deleted file mode 100644 index 136ea957be766e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610090 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "a839a360-94ae-4219-b1cc-458d836333a7", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json deleted file mode 100644 index 3cbdb6da3c141f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in a URI", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in a URI", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610090 or 2610092 or 2610093 or 2610094 or 2610095) or rule.id: (2610090 or 2610092 or 2610093 or 2610094 or 2610095))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules", - "This group of signatures detects double encoding of characters in an HTTP request. This is not something you should see on a typical network and could indicate an attempt to exploit the web application or bypass detections." - ], - "risk_score": 25, - "rule_id": "8aedfe6f-9219-463b-808b-91e7ea8ea5e8", - "severity": "low", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json deleted file mode 100644 index 986ac161d70df1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata eval PHP Function in an HTTP Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata eval PHP Function in an HTTP Request", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610088 or rule.id: 2610088)", - "risk_score": 50, - "rule_id": "8c77b4ed-4e98-438b-adb0-d645d4a4ea26", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json deleted file mode 100644 index 54b881428aa34c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Jenkins Chained Exploits CVE-2018-1000861 and CVE-2019-1003000", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Jenkins Chained Exploits CVE-2018-1000861 and CVE-2019-1003000", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027349 or 2027350) or rule.id: (2027349 or 2027350))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2018-1000861"], - "risk_score": 100, - "rule_id": "ada41f8a-92b1-49d0-80ac-c4bc28824ab5", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json deleted file mode 100644 index c050b73114bf52..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Possible Apache Axis RCE via SSRF (CVE-2019-0227)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Possible Apache Axis RCE via SSRF (CVE-2019-0227)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004698) or rule.id: (10004698))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0227"], - "risk_score": 100, - "rule_id": "2c8f321c-ba84-4c16-80dd-f20ea06e0c6d", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json deleted file mode 100644 index 9522a286f7898c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Apache Tomcat RCE on Windows (CVE-2019-0232)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Apache Tomcat RCE on Windows (CVE-2019-0232)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004953) or rule.id: (10004953))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0232"], - "risk_score": 100, - "rule_id": "fd7ef9a2-f010-49c1-8e08-31d84a9607dd", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json deleted file mode 100644 index 95940a5396b943..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Rails Arbitrary File Disclosure Attempt", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Rails Arbitrary File Disclosure Attempt", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027096) or rule.id: (2027096))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0604"], - "risk_score": 100, - "rule_id": "ec50104d-26b1-45a6-b80e-768bd13cc34c", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json deleted file mode 100644 index 401e1e815ea521..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT [NCC GROUP] Possible Bluekeep Inbound RDP Exploitation Attempt (CVE-2019-0708)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Possible Bluekeep Inbound RDP Exploitation Attempt (CVE-2019-0708)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004867 or 2027369) or rule.id: (10004867 or 2027369))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0708"], - "risk_score": 100, - "rule_id": "1589bff6-ec82-4acf-8f67-68ef0f3676d0", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json deleted file mode 100644 index 5f256681aedd9f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT IE Scripting Engine Memory Corruption Vulnerability (CVE-2019-0752)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT IE Scripting Engine Memory Corruption Vulnerability (CVE-2019-0752)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027721) or rule.id: (2027721))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0752"], - "risk_score": 100, - "rule_id": "5aa5f6db-2cc7-43de-ac8b-c7daa52ba9c3", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json deleted file mode 100644 index c470783b0266d3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Jenkins RCE CVE-2019-1003000", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Jenkins RCE CVE-2019-1003000", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027349 or 2027350 or 2027346) or rule.id: (2027349 or 2027350 or 2027346))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1003000"], - "risk_score": 100, - "rule_id": "6deba829-00ac-4298-bc80-976e4ef215d2", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json deleted file mode 100644 index 2c18ecc3104fd2..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Exim 4.87-4.91 RCE Attempt Inbound (CVE-2019-10149", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Exim 4.87-4.91 RCE Attempt Inbound (CVE-2019-10149", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027442) or rule.id: (2027442))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-10149"], - "risk_score": 100, - "rule_id": "e52d833a-0642-4076-89e9-6b7263361cee", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json deleted file mode 100644 index 0e2c8cfa7339d2..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SERVER Possible PHP Remote Code Execution CVE-2019-11043 PoC (Inbound)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SERVER Possible PHP Remote Code Execution CVE-2019-11043 PoC (Inbound)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028895) or rule.id: (2028895))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11043"], - "risk_score": 100, - "rule_id": "7955c692-1259-4f77-aa9e-95a98b69d4aa", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json deleted file mode 100644 index 65a6874f09932a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Pulse Secure SSL VPN - Arbitrary File Read (CVE-2019-11510)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Pulse Secure SSL VPN - Arbitrary File Read (CVE-2019-11510)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027904) or rule.id: (2027904))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11510"], - "risk_score": 100, - "rule_id": "d2dbbfee-2104-4d20-b562-d466b0b2c5ef", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json deleted file mode 100644 index 6e3e8bc8cdbb72..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Atlassian Crowd Plugin Upload Attempt (CVE-2019-11580)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Atlassian Crowd Plugin Upload Attempt (CVE-2019-11580)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027712) or rule.id: (2027712))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11580"], - "risk_score": 100, - "rule_id": "f6e6c803-b44c-44b1-acbb-cd3e5bca10f8", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json deleted file mode 100644 index 34b93871fa10b6..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Atlassian JIRA Template Injection RCE (CVE-2019-11581", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Atlassian JIRA Template Injection RCE (CVE-2019-11581", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027711) or rule.id: (2027711))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11581"], - "risk_score": 100, - "rule_id": "720663fb-23da-43a5-bf4f-907265e5426d", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json deleted file mode 100644 index ae014db82194eb..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Zoom Client Auto-Join (CVE-2019-13450", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Zoom Client Auto-Join (CVE-2019-13450", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027696) or rule.id: (2027696))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-13450"], - "risk_score": 100, - "rule_id": "04a9d926-51bb-4981-8116-04ee63f1ad75", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json deleted file mode 100644 index 5a70886a844699..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Appointment Hour Booking - WordPress Plugin - Stored XSS (CVE-2019-13505)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Appointment Hour Booking - WordPress Plugin - Stored XSS (CVE-2019-13505)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027706) or rule.id: (2027706))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-13505"], - "risk_score": 100, - "rule_id": "7b47f6a7-ae2a-46a1-a718-641649dfbfd6", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json deleted file mode 100644 index cbede3be1782bf..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SERVER Webmin RCE CVE-2019-15107", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SERVER Webmin RCE CVE-2019-15107", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027896) or rule.id: (2027896))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-15107"], - "risk_score": 100, - "rule_id": "37f923c4-048d-4a17-b804-b4f895477962", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json deleted file mode 100644 index 99ac06aa715aab..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible EXIM RCE Inbound (CVE-2019-15846)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible EXIM RCE Inbound (CVE-2019-15846)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027959 or 2027960) or rule.id: (2027959 or 2027960))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-15846"], - "risk_score": 100, - "rule_id": "1d625e03-a21b-40c8-82c0-edb497a48254", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json deleted file mode 100644 index 0fe9cde7307e8c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Enigma Network Management Systems v65.0.0 CVE-2019-16072 (Outbound)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Enigma Network Management Systems v65.0.0 CVE-2019-16072 (Outbound)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029159 or 2029158) or rule.id: (2029159 or 2029158))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16072"], - "risk_score": 100, - "rule_id": "5cf97dad-2327-4010-8498-64e5d53fd317", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json deleted file mode 100644 index 254c6019a039d3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Cisco RV320 RCE Attempt (CVE-2019-1652)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Cisco RV320 RCE Attempt (CVE-2019-1652)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2026860) or rule.id: (2026860))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1652"], - "risk_score": 100, - "rule_id": "ed220bf3-6617-41c3-8a03-8726d17e3dfc", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json deleted file mode 100644 index d804e7dc181739..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible rConfig 3.9.2 Remote Code Execution PoC (CVE-2019-16662)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible rConfig 3.9.2 Remote Code Execution PoC (CVE-2019-16662)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028933) or rule.id: (2028933))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16662"], - "risk_score": 100, - "rule_id": "777097d9-059e-409f-9509-67d7f90aea8c", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json deleted file mode 100644 index 7ceebbe31c0ea2..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT vBulletin 5.x Unauthenticated Remote Code Execution (CVE-2019-16759)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT vBulletin 5.x Unauthenticated Remote Code Execution (CVE-2019-16759)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028621 or 2028625 or 2028826) or rule.id: (2028621 or 2028625 or 2028826))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16759"], - "risk_score": 100, - "rule_id": "145634a6-6d3d-4e78-bd51-ffe6f69f6bbb", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json deleted file mode 100644 index 2c970e3248a642..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible EXIM DoS (CVE-2019-16928)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible EXIM DoS (CVE-2019-16928)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028636) or rule.id: (2028636))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16928"], - "risk_score": 100, - "rule_id": "39bb4ff1-ec7c-4379-9a07-ad24b83060bf", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json deleted file mode 100644 index 2ed70492f52cad..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Yachtcontrol Webservers RCE CVE-2019-17270", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Yachtcontrol Webservers RCE CVE-2019-17270", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029153 or 2029152) or rule.id: (2029153 or 2029152))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-17270"], - "risk_score": 100, - "rule_id": "e6f42ad9-c024-46de-99d8-492d780cdd5e", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json deleted file mode 100644 index 9c84f3042e86ce..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Cisco Prime Infrastruture RCE - CVE-2019-1821", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Cisco Prime Infrastruture RCE - CVE-2019-1821", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027368) or rule.id: (2027368))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1821"], - "risk_score": 100, - "rule_id": "5aed0105-a86a-4502-9a8b-169ee24b0c7f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json deleted file mode 100644 index 2ee5d4bff1cbe5..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Citrix Application Delivery Controller Arbitrary Code Execution Attempt (CVE-2019-19781) M2", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Citrix Application Delivery Controller Arbitrary Code Execution Attempt (CVE-2019-19781) M2", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029206 or 2029255) or rule.id: (2029206 or 2029255))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-19781"], - "risk_score": 100, - "rule_id": "6fde4e79-bf78-4173-b395-73377e289a73", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json deleted file mode 100644 index 7ca97786945ff1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Oracle Weblogic file upload RCE (CVE-2019-2618)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Oracle Weblogic file upload RCE (CVE-2019-2618)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004781) or rule.id: (10004781))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-2618"], - "risk_score": 100, - "rule_id": "7ba6a778-647c-4506-8314-8206cf31f513", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json deleted file mode 100644 index 66a7c63c9b3735..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Oracle Weblogic _async deserialization RCE Attempt (CVE-2019-2725)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Oracle Weblogic _async deserialization RCE Attempt (CVE-2019-2725)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004779 or 10004927) or rule.id: (10004779 or 10004927))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-2725"], - "risk_score": 100, - "rule_id": "f7879284-38e9-40d4-a471-6e1b38fd5a9f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json deleted file mode 100644 index b4a0f0284665dd..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_CLIENT Possible Confluence SSTI Exploitation Attempt - Leads to RCE/LFI (CVE-2019-3396)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_CLIENT Possible Confluence SSTI Exploitation Attempt - Leads to RCE/LFI (CVE-2019-3396)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004699 or 2027333) or rule.id: (10004699 or 2027333))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-3396"], - "risk_score": 100, - "rule_id": "d51ce0e4-31fa-4ffb-a1a6-7f9fa386ea52", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json deleted file mode 100644 index ae6e48baa0fa6a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Attempted Remote Command Injection Outbound (CVE-2019-3929)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Attempted Remote Command Injection Outbound (CVE-2019-3929)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027451 or 2027450) or rule.id: (2027451 or 2027450))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-3929"], - "risk_score": 100, - "rule_id": "0a6fefd6-22dd-4c78-aba8-e949b04360b4", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json deleted file mode 100644 index 42d9793336ae36..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT VMware VeloCloud Authorization Bypass (CVE-2019-5533)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT VMware VeloCloud Authorization Bypass (CVE-2019-5533)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028928) or rule.id: (2028928))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-5533"], - "risk_score": 100, - "rule_id": "65012760-1f26-47a3-b2d3-a685d638483f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json deleted file mode 100644 index cd55b6be262dcb..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Arbitrary PHP RCE in Drupal 8 < 8.5.11,8.6.10 (CVE-2019-6340)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Arbitrary PHP RCE in Drupal 8 < 8.5.11,8.6.10 (CVE-2019-6340)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004555) or rule.id: (10004555))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-6340"], - "risk_score": 100, - "rule_id": "4b2b4879-45c6-4721-b058-143f07aa474f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json deleted file mode 100644 index e8cfcb0cfc7916..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Linear eMerge E3 Unauthenticated Command Injection Inbound (CVE-2019-7256)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Linear eMerge E3 Unauthenticated Command Injection Inbound (CVE-2019-7256)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029207) or rule.id: (2029207))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-7256"], - "risk_score": 100, - "rule_id": "8ef47e09-39f5-494a-82b7-3aca4310ea96", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json deleted file mode 100644 index 0537004ae4b2d5..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_CLIENT Attempted RCE in Wordpress Social Warfare Plugin Inbound (CVE-2019-9978", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_CLIENT Attempted RCE in Wordpress Social Warfare Plugin Inbound (CVE-2019-9978", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027315) or rule.id: (2027315))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-9978"], - "risk_score": 100, - "rule_id": "6b185518-b84a-44b7-843c-01c95b5a2a83", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 8c36a7052a720a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata FTP Traffic on Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata FTP Traffic on Unusual Port, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610005 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "b1adc850-0fe3-4dac-94d3-6f240071f83a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 72228ce1215755..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata HTTP Traffic On Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata HTTP Traffic On Unusual Port, Internet Destination", - "query": " suricata.eve.alert.signature_id:2610001 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "43795909-913c-419d-8355-7f2880694bec", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 1f06fbb0a337db..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata IMAP Traffic on Unusual Port, internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata IMAP Traffic on Unusual Port, internet Destination", - "query": "suricata.eve.alert.signature_id:2610009 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "738ee70b-7d0f-438f-98ac-a393df58c58f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json deleted file mode 100644 index 9c2d818b88c5d3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata LaZagne Artifact in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata LaZagne Artifact in an HTTP POST", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610149 or 2610150)", - "risk_score": 50, - "rule_id": "c6e6f16f-66de-43d5-8ab7-599af536dedf", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json deleted file mode 100644 index 0cbf4092bfa31d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Mimikatz Artifacts in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Mimikatz Artifacts in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610155 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "1b62e8af-c10d-4708-9a74-118cb1c9ed8a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json deleted file mode 100644 index 730aaa63ab07db..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Mimikatz String Detected in HTTP Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Mimikatz String Detected in HTTP Response", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610144 or 2610145 or 2610146 or 2610147 or 2610148)", - "risk_score": 50, - "rule_id": "2b365d3a-11a3-4bec-9698-b36c908f46ff", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json deleted file mode 100644 index 96f180fee09902..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-DNS Traffic on TCP Port 53", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-DNS Traffic on TCP Port 53", - "query": "suricata.eve.alert.signature_id:2610014 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "67c7d28e-8be4-49ae-9c89-5c328ea245dc", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json deleted file mode 100644 index 95458f14b0b2c6..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-DNS Traffic on UDP Port 53", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-DNS Traffic on UDP Port 53", - "query": "suricata.eve.alert.signature_id:2610016 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "ba6dea7f-ba98-4a86-b570-d05d85472e79", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json deleted file mode 100644 index 42bcc2fa1bca19..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-FTP Traffic on Port 21", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-FTP Traffic on Port 21", - "query": "suricata.eve.alert.signature_id:2610006 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "ee2b07ec-94dd-48b2-b46b-7bef47cc43fc", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json deleted file mode 100644 index af681646e8224f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-HTTP Traffic on TCP Port 80", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-HTTP Traffic on TCP Port 80", - "query": "suricata.eve.alert.signature_id:2610002 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "70f9bd9f-accc-4da8-8674-38992096ddba", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json deleted file mode 100644 index 548b35165028c3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-IMAP Traffic on Port 1443 (IMAP)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-IMAP Traffic on Port 1443 (IMAP)", - "query": "suricata.eve.alert.signature_id:2610010 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "241b6a1d-4f73-4b68-bd98-22e909681930", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json deleted file mode 100644 index a7e57103c633d9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-SMB Traffic on TCP Port 139 (SMB)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-SMB Traffic on TCP Port 139 (SMB)", - "query": "suricata.eve.alert.signature_id:2610011 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "c259ab53-4b1a-42f6-b204-fe057c521515", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json deleted file mode 100644 index 3e07bd7a97cb85..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-SSH Traffic on Port 22", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-SSH Traffic on Port 22", - "query": "suricata.eve.alert.signature_id:2610008 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "256e9e8b-8366-4f23-8cbe-c9eb5ba25633", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json deleted file mode 100644 index 16dc9f46f0e32e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-TLS on TLS Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-TLS on TLS Port", - "query": "suricata.eve.alert.signature_id:2610004 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "b060c87f-af49-40eb-acee-561a1f1331aa", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json deleted file mode 100644 index e8bc59f1b5268a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Possible Cobalt Strike Malleable C2 Null Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Possible Cobalt Strike Malleable C2 Null Response", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610202 or 2610203)", - "risk_score": 50, - "rule_id": "6099a760-7293-4e26-8aa8-b984abb32ac6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json deleted file mode 100644 index 8b208e5586726a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Possible SQL Injection - SQL Commands in HTTP Transactions", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Possible SQL Injection - SQL Commands in HTTP Transactions", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610117 or 2610118 or 2610118 or 2610119 or 2610121 or 2610122 or 2610123)", - "risk_score": 50, - "rule_id": "cdfbcd5e-1d8e-47e6-b3f2-b09bce780640", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json deleted file mode 100644 index fe3d500b42d3e9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata RPC Traffic on HTTP Ports", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata RPC Traffic on HTTP Ports", - "query": "suricata.eve.alert.signature_id:2610012 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "87e77fb6-b555-43be-adc5-f57c6aaf7cd0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json deleted file mode 100644 index a59cc42fa4557f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Serialized PHP Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Serialized PHP Detected", - "query": "suricata.eve.alert.signature_id:2610091 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "3baa5b65-d11e-40fb-a9b4-6b2a6a062d48", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json deleted file mode 100644 index e4fd0e866e7cf9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata shell_exec PHP Function in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata shell_exec PHP Function in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610087 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "082fca48-4707-485a-aedb-340ee77e0687", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json deleted file mode 100644 index a22c3a4fdfdd40..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata SSH Traffic Not on Port 22, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata SSH Traffic Not on Port 22, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610007 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "82265eef-1212-4c4f-af04-f977a3060592", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 23f1f79bc42487..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata TLS Traffic on Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata TLS Traffic on Unusual Port, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610003 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "6c1db8ba-db4b-4513-a0e3-b3c857ba8b05", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json deleted file mode 100644 index 9717beac902e5f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Windows Executable Served by JPEG Web Content", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Windows Executable Served by JPEG Web Content", - "query": "suricata.eve.alert.signature_id:2610084 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "f7f038f4-b97a-4d0c-b3b6-d5fa1ad15951", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json deleted file mode 100644 index 87549a455c1d3e..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Detected Zeek capture loss exceeds the percentage threshold", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice CaptureLoss::Too_Much_Loss", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"CaptureLoss::Too_Much_Loss\" or rule.name: \"CaptureLoss::Too_Much_Loss\")", - "risk_score": 50, - "rule_id": "c115a407-799b-45d6-962e-a639bb764c06", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json deleted file mode 100644 index 69a82f9840a931..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Data has sequence hole; perhaps due to filtering.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Conn::Content_Gap", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Conn::Content_Gap\" or rule.name: \"Conn::Content_Gap\")", - "risk_score": 50, - "rule_id": "22d12b64-33f4-40ce-ad57-49dd870bc8e5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json deleted file mode 100644 index c5ba4eb8082aaf..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Possible evasion; usually just chud.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Conn::Retransmission_Inconsistency", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Conn::Retransmission_Inconsistency\" or rule.name: \"Conn::Retransmission_Inconsistency\")", - "risk_score": 50, - "rule_id": "53719624-55f0-4541-8370-f27f6766fb9e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json deleted file mode 100644 index cb5db1529aa0ec..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Raised when a non-local name is found to be pointing at a local host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice DNS::External_Name", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"DNS::External_Name\" or rule.name: \"DNS::External_Name\")", - "risk_score": 50, - "rule_id": "39c40c5a-110c-45b1-876f-969212e8814b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json deleted file mode 100644 index 43bc1f05a2212f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a host bruteforcing FTP logins by watching for too many rejected usernames or failed passwords.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice FTP::Bruteforcing", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"FTP::Bruteforcing\" or rule.name: \"FTP::Bruteforcing\")", - "risk_score": 50, - "rule_id": "7e069475-817e-4e89-9245-1dfaa3083b11", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json deleted file mode 100644 index 63b8b847563b57..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a successful response to a “SITE EXEC” command/arg pair was seen.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice FTP::Site_Exec_Success", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"FTP::Site_Exec_Success\" or rule.name: \"FTP::Site_Exec_Success\")", - "risk_score": 50, - "rule_id": "4b9cb3e9-e26a-4bd2-bd1f-8d451b49838f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json deleted file mode 100644 index adc8878f6986aa..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performed a heartbleed attack or scan.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Attack", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Attack\" or rule.name: \"Heartbleed::SSL_Heartbeat_Attack\")", - "risk_score": 50, - "rule_id": "68a33102-3680-4581-a48a-210b23925905", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json deleted file mode 100644 index 3f03e5483cc315..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performing a heartbleed attack was probably successful.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Attack_Success", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Attack_Success\" or rule.name: \"Heartbleed::SSL_Heartbeat_Attack_Success\")", - "risk_score": 50, - "rule_id": "241a61ae-b385-4f36-96c4-b2fb5446cc43", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json deleted file mode 100644 index 2902c4a4b8e5fe..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates we saw many heartbeat requests without a reply. Might be an attack.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Many_Requests", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Many_Requests\" or rule.name: \"Heartbleed::SSL_Heartbeat_Many_Requests\")", - "risk_score": 50, - "rule_id": "59d6a32c-753e-4c19-bb77-1befdc6e0e6a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json deleted file mode 100644 index 871999b842609a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates we saw heartbeat requests with odd length. Probably an attack or scan.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Odd_Length", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Odd_Length\" or rule.name: \"Heartbleed::SSL_Heartbeat_Odd_Length\")", - "risk_score": 50, - "rule_id": "0c6e7be4-6cab-4ee1-ad51-7c1ffd0e9002", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json deleted file mode 100644 index fe6bcb8a881003..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performing SQL injection attacks was detected.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice HTTP::SQL_Injection_Attacker", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"HTTP::SQL_Injection_Attacker\" or rule.name: \"HTTP::SQL_Injection_Attacker\")", - "risk_score": 50, - "rule_id": "4ca9ef93-7e7e-40a4-8d71-9130204d86e6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json deleted file mode 100644 index ed1f5bbaa13b2a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host was seen to have SQL injection attacks against it. This is tracked by IP address as opposed to hostname.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice HTTP::SQL_Injection_Victim", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"HTTP::SQL_Injection_Victim\" or rule.name: \"HTTP::SQL_Injection_Victim\")", - "risk_score": 50, - "rule_id": "dda43d7f-69bc-487f-b05c-2b518e9db622", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json deleted file mode 100644 index 615f3b48276567..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice is generated when an intelligence indicator is denoted to be notice-worthy.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Intel::Notice", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Intel::Notice\" or rule.name: \"Intel::Notice\")", - "risk_score": 50, - "rule_id": "122e153a-78f3-4e7e-a5b5-cfe0b917f109", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json deleted file mode 100644 index cbe9fd654c4f80..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Zeek notice reporting a count of how often a notice occurred.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Notice::Tally", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Notice::Tally\" or rule.name: \"Notice::Tally\")", - "risk_score": 50, - "rule_id": "7581fd81-25e8-489e-bcf3-69db068b7a6c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json deleted file mode 100644 index 2d35d42eb07a1d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Limitations in BPF make shunting some connections with BPF impossible. This notice encompasses those various cases.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Cannot_BPF_Shunt_Conn", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Cannot_BPF_Shunt_Conn\" or rule.name: \"PacketFilter::Cannot_BPF_Shunt_Conn\")", - "risk_score": 50, - "rule_id": "0031d83e-1fb4-4dd6-b938-97ae7044b051", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json deleted file mode 100644 index 4013b77fe6e4ce..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice is generated if a packet filter cannot be compiled.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Compile_Failure", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Compile_Failure\" or rule.name: \"PacketFilter::Compile_Failure\")", - "risk_score": 50, - "rule_id": "335b2ddc-f806-46e8-8ffa-114d613aac92", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json deleted file mode 100644 index 21229e4055f480..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates packets were dropped by the packet filter.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Dropped_Packets", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Dropped_Packets\" or rule.name: \"PacketFilter::Dropped_Packets\")", - "risk_score": 50, - "rule_id": "4f212278-329b-4088-ae59-9091003dff22", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json deleted file mode 100644 index 6f6ff30f99b570..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated if a packet filter fails to install.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Install_Failure", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Install_Failure\" or rule.name: \"PacketFilter::Install_Failure\")", - "risk_score": 50, - "rule_id": "235988ec-d037-4f5f-a211-74106512b36d", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json deleted file mode 100644 index 0785959078bb71..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicative that PacketFilter::max_bpf_shunts connections are already being shunted with BPF filters and no more are allowed.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::No_More_Conn_Shunts_Available", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::No_More_Conn_Shunts_Available\" or rule.name: \"PacketFilter::No_More_Conn_Shunts_Available\")", - "risk_score": 50, - "rule_id": "de4016de-3374-41a0-a678-21d36c70af9a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json deleted file mode 100644 index e8dbcaaeec43e0..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated when a notice takes too long to compile.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Too_Long_To_Compile_Filter", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Too_Long_To_Compile_Filter\" or rule.name: \"PacketFilter::Too_Long_To_Compile_Filter\")", - "risk_score": 50, - "rule_id": "71e93c42-7990-4233-a8a5-2631193df7db", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json deleted file mode 100644 index 0caf01e3823c9b..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a protocol was detected on a non-standard port.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice ProtocolDetector::Protocol_Found", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"ProtocolDetector::Protocol_Found\" or rule.name: \"ProtocolDetector::Protocol_Found\")", - "risk_score": 50, - "rule_id": "777586b6-4757-489e-a6e8-676b7df70b39", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json deleted file mode 100644 index 196c9dc7241c8c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a server was detected on a non-standard port for the protocol.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice ProtocolDetector::Server_Found", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"ProtocolDetector::Server_Found\" or rule.name: \"ProtocolDetector::Server_Found\")", - "risk_score": 50, - "rule_id": "7d7f7635-6900-4f63-b14b-477a909ea90a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json deleted file mode 100644 index 34c8a126e424c0..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Address scans detect that a host appears to be scanning some number of destinations on a single port.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Scan::Address_Scan", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Scan::Address_Scan\" or rule.name: \"Scan::Address_Scan\")", - "risk_score": 50, - "rule_id": "9d320fca-4ec1-4511-bdbc-7edf9673c07d", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json deleted file mode 100644 index 1334f2c08ad09f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Port scans detect that an attacking host appears to be scanning a single victim host on several ports.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Scan::Port_Scan", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Scan::Port_Scan\" or rule.name: \"Scan::Port_Scan\")", - "risk_score": 50, - "rule_id": "d09fbf7a-47a7-4130-8dd7-b386cca81a42", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json deleted file mode 100644 index 1dc25388dc688f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The same signature has triggered multiple times for a host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Count_Signature", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Count_Signature\" or rule.name: \"Signatures::Count_Signature\")", - "risk_score": 50, - "rule_id": "a704589c-8ba9-4a3c-8e39-ab9360cade17", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json deleted file mode 100644 index 06cf39c1c3dbdd..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Host has triggered the same signature on multiple hosts.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Multiple_Sig_Responders", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Multiple_Sig_Responders\" or rule.name: \"Signatures::Multiple_Sig_Responders\")", - "risk_score": 50, - "rule_id": "4f313ae8-cbc6-4082-9599-526f8ccb7303", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json deleted file mode 100644 index 350e6dfc30e187..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Host has triggered many signatures on the same host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Multiple_Signatures", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Multiple_Signatures\" or rule.name: \"Signatures::Multiple_Signatures\")", - "risk_score": 50, - "rule_id": "ab90d81c-79e1-4f62-a61e-484c4bedb2b0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json deleted file mode 100644 index c1438edf2e4acf..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generic notice type for notice-worthy signature matches.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Sensitive_Signature", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Sensitive_Signature\" or rule.name: \"Signatures::Sensitive_Signature\")", - "risk_score": 50, - "rule_id": "ac394dec-67e8-417f-bb06-ae0bd75556b0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json deleted file mode 100644 index 7fd878ceb6c7f8..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Summarize the number of times a host triggered a signature.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Signature_Summary", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Signature_Summary\" or rule.name: \"Signatures::Signature_Summary\")", - "risk_score": 50, - "rule_id": "d17fe857-eb67-4843-ab63-bf4852e49396", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json deleted file mode 100644 index 1e2579dfd1b4ec..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The originator’s address is seen in the block list error message. This is useful to detect local hosts sending SPAM with a high positive rate.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Blocklist_Blocked_Host", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Blocklist_Blocked_Host\" or rule.name: \"SMTP::Blocklist_Blocked_Host\")", - "risk_score": 50, - "rule_id": "402d5f78-82cd-4320-8b69-3185e44daf07", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json deleted file mode 100644 index ae4794bd5481f4..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "An SMTP server sent a reply mentioning an SMTP block list.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Blocklist_Error_Message", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Blocklist_Error_Message\" or rule.name: \"SMTP::Blocklist_Error_Message\")", - "risk_score": 50, - "rule_id": "b9bb4a93-8c5c-4942-9193-e2dc97230034", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json deleted file mode 100644 index ed871f4aa68986..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "SMTP message orignated from country or network configured to be suspicious.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Suspicious_Origination", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Suspicious_Origination\" or rule.name: \"SMTP::Suspicious_Origination\")", - "risk_score": 50, - "rule_id": "cc6e9fef-d936-4faf-8936-e576c089d8b2", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json deleted file mode 100644 index 5a5cd3f48245f9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that an interesting software application changed versions on a host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Software::Software_Version_Change", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Software::Software_Version_Change\" or rule.name: \"Software::Software_Version_Change\")", - "risk_score": 50, - "rule_id": "ea1d2c1b-ecfe-42a5-bd0b-56c7a1bd8075", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json deleted file mode 100644 index 8addd5ed395624..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a vulnerable version of software was detected.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Software::Vulnerable_Version", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Software::Vulnerable_Version\" or rule.name: \"Software::Vulnerable_Version\")", - "risk_score": 50, - "rule_id": "97b4d80c-7671-4301-85a6-954aa0ba96ce", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json deleted file mode 100644 index f69ab099bf6d98..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated if a login originates or responds with a host where the reverse hostname lookup resolves to a name matched by the SSH::interesting_hostnames regular expression.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Interesting_Hostname_Login", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Interesting_Hostname_Login\" or rule.name: \"SSH::Interesting_Hostname_Login\")", - "risk_score": 50, - "rule_id": "6a7f2b0a-3f24-4d58-aa84-243f1f0556d9", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json deleted file mode 100644 index 3b12aae2f4dd8a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host previously identified as a \"password guesser\" has now had a successful login attempt.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Login_By_Password_Guesser", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Login_By_Password_Guesser\" or rule.name: \"SSH::Login_By_Password_Guesser\")", - "risk_score": 50, - "rule_id": "5600ad95-2244-43db-8a7d-77eea95f80db", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json deleted file mode 100644 index 4fd7e8ec15ed70..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host has been identified as crossing the SSH::password_guesses_limit threshold with failed logins.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Password_Guessing", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Password_Guessing\" or rule.name: \"SSH::Password_Guessing\")", - "risk_score": 50, - "rule_id": "e278142a-4ee7-4443-9b1f-421174b0dabf", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json deleted file mode 100644 index ecd57510441ae0..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "SSH login was seen to or from a \"watched\" country based on the SSH::watched_countries variable", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Watched_Country_Login", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Watched_Country_Login\" or rule.name: \"SSH::Watched_Country_Login\")", - "risk_score": 50, - "rule_id": "983f4b7e-38cd-4d7f-8be6-40447431561e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json deleted file mode 100644 index 0309896ed31eea..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate’s NotValidAfter date has lapsed and the certificate is now invalid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Expired", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Expired\" or rule.name: \"SSL::Certificate_Expired\")", - "risk_score": 50, - "rule_id": "3981f48e-49a5-4a3e-9b44-900a0887526c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json deleted file mode 100644 index 8f76bdab1a7ea3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate is going to expire within SSL::notify_when_cert_expiring_in.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Expires_Soon", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Expires_Soon\" or rule.name: \"SSL::Certificate_Expires_Soon\")", - "risk_score": 50, - "rule_id": "e8207172-3478-4b2c-85b7-6f13d97fff43", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json deleted file mode 100644 index 785ba45744022c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate’s NotValidBefore date is future dated.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Not_Valid_Yet", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Not_Valid_Yet\" or rule.name: \"SSL::Certificate_Not_Valid_Yet\")", - "risk_score": 50, - "rule_id": "45586490-99f6-4e11-8228-2229d727a3b4", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json deleted file mode 100644 index 3704a1be0cd269..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This indicates that the OCSP response was not deemed to be valid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Invalid_Ocsp_Response", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Invalid_Ocsp_Response\" or rule.name: \"SSL::Invalid_Ocsp_Response\")", - "risk_score": 50, - "rule_id": "eb17fcbb-de22-4aa0-81aa-1c059bdd4f2b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json deleted file mode 100644 index c068a3ecf0d82c..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice indicates that the result of validating the certificate along with its full certificate chain was invalid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Invalid_Server_Cert", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Invalid_Server_Cert\" or rule.name: \"SSL::Invalid_Server_Cert\")", - "risk_score": 50, - "rule_id": "13f51fe0-fc74-4c45-90f3-6fb1cd26ec66", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json deleted file mode 100644 index 8d180115eadeac..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe version", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Old_Version", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Old_Version\" or rule.name: \"SSL::Old_Version\")", - "risk_score": 50, - "rule_id": "260b680e-c3d6-4c03-90cd-03c86e9f8ec1", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json deleted file mode 100644 index 602445d1463fe3..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe cipher", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Weak_Cipher", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Weak_Cipher\" or rule.name: \"SSL::Weak_Cipher\")", - "risk_score": 50, - "rule_id": "25886074-6ae1-41c0-8546-e8cf55ed1b4b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json deleted file mode 100644 index b88752e9b8c945..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe key.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Weak_Key", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Weak_Key\" or rule.name: \"SSL::Weak_Key\")", - "risk_score": 50, - "rule_id": "e020f504-c0e5-4768-8e1f-1e2ec7bac961", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json deleted file mode 100644 index 8a36b974dc4fc6..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The hash value of a file transferred over HTTP matched in the malware hash registry.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice TeamCymruMalwareHashRegistry::Match", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"TeamCymruMalwareHashRegistry::Match\" or rule.name: \"TeamCymruMalwareHashRegistry::Match\")", - "risk_score": 50, - "rule_id": "a130a0ba-b083-4630-b0ea-cceb80d7720b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json deleted file mode 100644 index ec05000118f35d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host was seen running traceroutes.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Traceroute::Detected", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Traceroute::Detected\" or rule.name: \"Traceroute::Detected\")", - "risk_score": 50, - "rule_id": "aeefe077-f05d-44a7-b757-272fc51c334c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json deleted file mode 100644 index dcc5dfcf124ca0..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generic unusual but notice-worthy weird activity.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Weird::Activity", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Weird::Activity\" or rule.name: \"Weird::Activity\")", - "risk_score": 50, - "rule_id": "d5ad39d0-8421-4f79-ad93-8ddbf7f553b3", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} From b5c6c9a3e33744eab5c8216e94b9d1b890dd654e Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 21 Jan 2020 21:56:35 -0700 Subject: [PATCH 46/59] [SIEM][Detection Engine] Tags being turned into null ## Summary Test: 1.) Add a tag to a new rule 2.) Activate the rule 3.) Ensure the tag does not go away ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ ~~- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~~ ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../siem/server/lib/detection_engine/rules/update_rules.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index e2632791f859e3..f6932fc37d85af 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -173,12 +173,11 @@ export const updateRules = async ({ } else { // enabled is null or undefined and we do not touch the rule } - return alertsClient.update({ id: rule.id, data: { tags: addTags( - tags, + tags != null ? tags : rule.tags, // Add tags as an update if it exists, otherwise re-use the older tags rule.params.ruleId, immutable != null ? immutable : rule.params.immutable // Add new one if it exists, otherwise re-use old one ), From 5e711e423a9dab4f3c8c315c978f293119762b1a Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 22 Jan 2020 07:08:58 +0100 Subject: [PATCH 47/59] [ML] Fix counters and percentages for array fields on the Data visualizer page (#55209) * [ML] update data visualizer endpoint to check doc counts * [ML] fix mock for cardinality tests * [ML] use actual field name for agg filtering instead of safeFieldName Co-authored-by: Elastic Machine --- .../models/data_visualizer/data_visualizer.js | 48 +++++++++++++++---- .../__tests__/mock_farequote_cardinality.json | 8 +++- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js b/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js index 7c2e3eaf07bccc..f4ee032ee2dbbe 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js +++ b/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js @@ -261,7 +261,7 @@ export class DataVisualizer { aggregatableFields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field, i); aggs[`${safeFieldName}_count`] = { - value_count: { field }, + filter: { exists: { field } }, }; aggs[`${safeFieldName}_cardinality`] = { cardinality: { field }, @@ -296,7 +296,7 @@ export class DataVisualizer { samplerShardSize > 0 ? _.get(aggregations, ['sample', 'doc_count'], 0) : totalCount; aggregatableFields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field, i); - const count = _.get(aggregations, [...aggsPath, `${safeFieldName}_count`, 'value'], 0); + const count = _.get(aggregations, [...aggsPath, `${safeFieldName}_count`, 'doc_count'], 0); if (count > 0) { const cardinality = _.get( aggregations, @@ -433,7 +433,12 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_field_stats`] = { - stats: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, + aggs: { + actual_stats: { + stats: { field: field.fieldName }, + }, + }, }; aggs[`${safeFieldName}_percentiles`] = { percentiles: { @@ -484,10 +489,19 @@ export class DataVisualizer { const batchStats = []; fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); - const fieldStatsResp = _.get(aggregations, [...aggsPath, `${safeFieldName}_field_stats`], {}); + const docCount = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'doc_count'], + 0 + ); + const fieldStatsResp = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'actual_stats'], + {} + ); const stats = { fieldName: field.fieldName, - count: _.get(fieldStatsResp, 'count', 0), + count: docCount, min: _.get(fieldStatsResp, 'min', 0), max: _.get(fieldStatsResp, 'max', 0), avg: _.get(fieldStatsResp, 'avg', 0), @@ -632,7 +646,12 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_field_stats`] = { - stats: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, + aggs: { + actual_stats: { + stats: { field: field.fieldName }, + }, + }, }; }); @@ -651,10 +670,19 @@ export class DataVisualizer { const batchStats = []; fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); - const fieldStatsResp = _.get(aggregations, [...aggsPath, `${safeFieldName}_field_stats`], {}); + const docCount = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'doc_count'], + 0 + ); + const fieldStatsResp = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'actual_stats'], + {} + ); batchStats.push({ fieldName: field.fieldName, - count: _.get(fieldStatsResp, 'count', 0), + count: docCount, earliest: _.get(fieldStatsResp, 'min', 0), latest: _.get(fieldStatsResp, 'max', 0), }); @@ -680,7 +708,7 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_value_count`] = { - value_count: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, }; aggs[`${safeFieldName}_values`] = { terms: { @@ -707,7 +735,7 @@ export class DataVisualizer { const safeFieldName = getSafeAggregationName(field.fieldName, i); const stats = { fieldName: field.fieldName, - count: _.get(aggregations, [...aggsPath, `${safeFieldName}_value_count`, 'value'], 0), + count: _.get(aggregations, [...aggsPath, `${safeFieldName}_value_count`, 'doc_count'], 0), trueCount: 0, falseCount: 0, }; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json b/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json index 9dcfc11575abb8..8d408ff0310c91 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json +++ b/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json @@ -1 +1,7 @@ -{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":86274,"max_score":0,"hits":[]},"aggregations":{"airline_cardinality":{"value":19},"airline_count":{"value":86274}}} +{ + "took": 0, + "timed_out": false, + "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, + "hits": { "total": 86274, "max_score": 0, "hits": [] }, + "aggregations": { "airline_cardinality": { "value": 19 }, "airline_count": { "doc_count": 86274 } } +} From 34333aa1fde31f67c640feccd34136a07e39432e Mon Sep 17 00:00:00 2001 From: Ben Skelker <54019610+benskelker@users.noreply.github.com> Date: Wed, 22 Jan 2020 10:29:54 +0200 Subject: [PATCH 48/59] fixes typos (#55521) --- .../rules/components/pre_packaged_rules/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts index 5f89bd072ebed8..e70eadda570858 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts @@ -17,7 +17,7 @@ export const PRE_BUILT_MSG = i18n.translate( 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', { defaultMessage: - 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met.By default, all prebuilt rules are disabled and you select which rules you want to activate', + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', } ); From 1f10da865a085d6ea46f78e6cd0d34b40b8a11e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Wed, 22 Jan 2020 15:02:39 +0530 Subject: [PATCH 49/59] [Mappings editor] Add missing relations parameter (#55003) --- .../hook_form_lib/components/use_array.ts | 21 +- .../hook_form_lib/components/use_field.tsx | 14 +- .../forms/hook_form_lib/hooks/use_form.ts | 6 + .../eager_global_ordinals_parameter.tsx | 27 +- .../document_fields/field_parameters/index.ts | 8 + .../field_parameters/relations_parameter.tsx | 268 ++++++++++++++++++ .../fields/field_types/index.ts | 2 + .../fields/field_types/join_type.tsx | 37 +++ .../constants/parameters_definition.tsx | 11 + .../mappings_editor/lib/serializers.ts | 27 +- .../mappings_editor/shared_imports.ts | 3 + .../app/components/mappings_editor/types.ts | 2 + .../public/app/services/documentation.ts | 4 + 13 files changed, 415 insertions(+), 15 deletions(-) create mode 100644 x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx create mode 100644 x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts index d03ff93a47218a..3f71f83c55694c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts @@ -17,13 +17,14 @@ * under the License. */ -import { useState, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useFormContext } from '../form_context'; interface Props { path: string; initialNumberOfItems?: number; + readDefaultValueOnForm?: boolean; children: (args: { items: ArrayItem[]; addItem: () => void; @@ -52,9 +53,15 @@ export interface ArrayItem { * * Look at the README.md for some examples. */ -export const UseArray = ({ path, initialNumberOfItems, children }: Props) => { +export const UseArray = ({ + path, + initialNumberOfItems, + readDefaultValueOnForm = true, + children, +}: Props) => { + const didMountRef = useRef(false); const form = useFormContext(); - const defaultValues = form.getFieldDefaultValue(path) as any[]; + const defaultValues = readDefaultValueOnForm && (form.getFieldDefaultValue(path) as any[]); const uniqueId = useRef(0); const getInitialItemsFromValues = (values: any[]): ArrayItem[] => @@ -99,5 +106,13 @@ export const UseArray = ({ path, initialNumberOfItems, children }: Props) => { }); }; + useEffect(() => { + if (didMountRef.current) { + setItems(updatePaths(items)); + } else { + didMountRef.current = true; + } + }, [path]); + return children({ items, addItem, removeItem }); }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 021d52fbe9edb7..bdcf47c8657019 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -29,17 +29,27 @@ export interface Props { defaultValue?: unknown; component?: FunctionComponent | 'input'; componentProps?: Record; + readDefaultValueOnForm?: boolean; onChange?: (value: unknown) => void; children?: (field: FieldHook) => JSX.Element; } export const UseField = React.memo( - ({ path, config, defaultValue, component, componentProps, onChange, children }: Props) => { + ({ + path, + config, + defaultValue, + component, + componentProps, + readDefaultValueOnForm = true, + onChange, + children, + }: Props) => { const form = useFormContext(); component = component === undefined ? 'input' : component; componentProps = componentProps === undefined ? {} : componentProps; - if (typeof defaultValue === 'undefined') { + if (typeof defaultValue === 'undefined' && readDefaultValueOnForm) { defaultValue = form.getFieldDefaultValue(path); } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index d8b2f35e117a60..2ee68eb4d7a1a9 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -198,6 +198,12 @@ export function useForm( }); formData$.current.next(currentFormData as T); + + /** + * After removing a field, the form validity might have changed + * (an invalid field might have been removed and now the form is valid) + */ + updateFormValidity(); }; const setFieldValue: FormHook['setFieldValue'] = (fieldName, value) => { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx index ecd9715ea295db..e6acf288331b52 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx @@ -8,21 +8,31 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { ParameterName } from '../../../types'; import { EditFieldFormRow } from '../fields/edit_field'; import { documentationService } from '../../../../../services/documentation'; -export const EagerGlobalOrdinalsParameter = () => ( +interface Props { + configPath?: ParameterName; + description?: string | JSX.Element; +} + +export const EagerGlobalOrdinalsParameter = ({ + description, + configPath = 'eager_global_ordinals', +}: Props) => ( ( href: documentationService.getEagerGlobalOrdinalsLink(), }} formFieldPath="eager_global_ordinals" + configPath={configPath} /> ); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts index b248776c884f12..94a49012f1c3b3 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { relationsSerializer, relationsDeserializer } from './relations_parameter'; + export * from './name_parameter'; export * from './index_parameter'; @@ -53,3 +55,9 @@ export * from './split_queries_on_whitespace_parameter'; export * from './locale_parameter'; export * from './max_shingle_size_parameter'; + +export * from './relations_parameter'; + +export const PARAMETER_SERIALIZERS = [relationsSerializer]; + +export const PARAMETER_DESERIALIZERS = [relationsDeserializer]; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx new file mode 100644 index 00000000000000..1fe01968b09832 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx @@ -0,0 +1,268 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiButtonEmpty, + EuiToolTip, + EuiButtonIcon, + EuiSpacer, + EuiCallOut, + EuiLink, + EuiBasicTable, + EuiBasicTableColumn, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + UseField, + UseArray, + ArrayItem, + FieldConfig, + TextField, + ComboBoxField, +} from '../../../shared_imports'; +import { Field } from '../../../types'; + +import { documentationService } from '../../../../../services/documentation'; +import { EditFieldFormRow } from '../fields/edit_field'; + +// This is the Elasticsearch interface to declare relations +interface RelationsES { + [parent: string]: string | string[]; +} + +// Internally we will use this type for "relations" as it is more UI friendly +// to loop over the relations and its children +type RelationsInternal = Array<{ parent: string; children: string[] }>; + +/** + * Export custom serializer to be used when we need to serialize the form data to be sent to ES + * @param field The field to be serialized + */ +export const relationsSerializer = (field: Field): Field => { + if (field.relations === undefined) { + return field; + } + + const relations = field.relations as RelationsInternal; + const relationsSerialized = relations.reduce( + (acc, item) => ({ + ...acc, + [item.parent]: item.children.length === 1 ? item.children[0] : item.children, + }), + {} as RelationsES + ); + + return { + ...field, + relations: relationsSerialized, + }; +}; + +/** + * Export custom deserializer to be used when we need to deserialize the data coming from ES + * @param field The field to be serialized + */ +export const relationsDeserializer = (field: Field): Field => { + if (field.relations === undefined) { + return field; + } + + const relations = field.relations as RelationsES; + const relationsDeserialized = Object.entries(relations).map(([parent, children]) => ({ + parent, + children: typeof children === 'string' ? [children] : children, + })); + + return { + ...field, + relations: relationsDeserialized, + }; +}; + +const childConfig: FieldConfig = { + defaultValue: [], +}; + +export const RelationsParameter = () => { + const renderWarning = () => ( + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.join.multiLevelsPerformanceDocumentationLink', + { + defaultMessage: 'denormalize your data.', + } + )} + + ), + }} + /> + } + /> + ); + + return ( + + + {({ items, addItem, removeItem }) => { + const columns: Array> = [ + // Parent column + { + name: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.parentColumnTitle', + { + defaultMessage: 'Parent', + } + ), + render: (item: ArrayItem) => { + // By adding ".parent" to the path, we are saying that we want an **object** + // to be created for each array item. + // This object will have a "parent" property with the field value. + return ( +
+ +
+ ); + }, + }, + // Children column (ComboBox) + { + name: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.childrenColumnTitle', + { + defaultMessage: 'Children', + } + ), + render: (item: ArrayItem) => { + return ( +
+ +
+ ); + }, + }, + // Actions column + { + width: '48px', + actions: [ + { + render: ({ id }: ArrayItem) => { + const label = i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.removeRelationshipTooltipLabel', + { + defaultMessage: 'Remove relationship', + } + ); + return ( + + removeItem(id)} + /> + + ); + }, + }, + ], + }, + ]; + + return ( + <> + {items.length > 1 && ( + <> + {renderWarning()} + + + )} + + + + {/* Add relation button */} + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.addRelationshipButtonLabel', + { + defaultMessage: 'Add relationship', + } + )} + + + ); + }} +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts index 5b81c525804c98..bad41d1f88efcc 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts @@ -24,6 +24,7 @@ import { SearchAsYouType } from './search_as_you_type'; import { FlattenedType } from './flattened_type'; import { ShapeType } from './shape_type'; import { DenseVectorType } from './dense_vector_type'; +import { JoinType } from './join_type'; const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { alias: AliasType, @@ -44,6 +45,7 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { flattened: FlattenedType, shape: ShapeType, dense_vector: DenseVectorType, + join: JoinType, }; export const getParametersFormForType = ( diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx new file mode 100644 index 00000000000000..688786ae25f8c9 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { BasicParametersSection, AdvancedParametersSection } from '../edit_field'; +import { RelationsParameter, EagerGlobalOrdinalsParameter } from '../../field_parameters'; + +const i18nTexts = { + eagerGlobalOrdinalsDescription: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.join.eagerGlobalOrdinalsFieldDescription', + { + defaultMessage: + 'The join field uses global ordinals to speed up joins. By default, if the index has changed, global ordinals for the join field will be rebuilt as part of the refresh. This can add significant time to the refresh, however most of the times this is the right trade-off.', + } + ), +}; + +export const JoinType = () => { + return ( + <> + + + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx index ba6a121eb7cdbb..2ea525bdb522f5 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx @@ -663,6 +663,11 @@ export const PARAMETERS_DEFINITION = { }, schema: t.boolean, }, + eager_global_ordinals_join: { + fieldConfig: { + defaultValue: true, + }, + }, index_phrases: { fieldConfig: { defaultValue: false, @@ -894,6 +899,12 @@ export const PARAMETERS_DEFINITION = { }, schema: t.string, }, + relations: { + fieldConfig: { + defaultValue: [] as any, // Needed for FieldParams typing + }, + schema: t.record(t.string, t.union([t.string, t.array(t.string)])), + }, max_shingle_size: { fieldConfig: { type: FIELD_TYPES.SELECT, diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts index f57f0bb9d87dec..131d886ff05d95 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts @@ -5,6 +5,10 @@ */ import { SerializerFunc } from '../shared_imports'; +import { + PARAMETER_SERIALIZERS, + PARAMETER_DESERIALIZERS, +} from '../components/document_fields/field_parameters'; import { Field, DataType, MainType, SubType } from '../types'; import { INDEX_DEFAULT, MAIN_DATA_TYPE_DEFINITION } from '../constants'; import { getMainTypeFromSubType } from './utils'; @@ -21,6 +25,25 @@ const sanitizeField = (field: Field): Field => {} as any ); +/** + * Run custom parameter serializers on field. + * Each serializer takes the field as single argument and returns it serialized in an immutable way. + * @param field The field we are serializing + */ +const runParametersSerializers = (field: Field): Field => + PARAMETER_SERIALIZERS.reduce((fieldSerialized, serializer) => serializer(fieldSerialized), field); + +/** + * Run custom parameter deserializers on field. + * Each deserializer takes the field as single argument and returns it deserialized in an immutable way. + * @param field The field we are deserializing + */ +const runParametersDeserializers = (field: Field): Field => + PARAMETER_DESERIALIZERS.reduce( + (fieldDeserialized, serializer) => serializer(fieldDeserialized), + field + ); + export const fieldSerializer: SerializerFunc = (field: Field) => { // If a subType is present, use it as type for ES if ({}.hasOwnProperty.call(field, 'subType')) { @@ -31,7 +54,7 @@ export const fieldSerializer: SerializerFunc = (field: Field) => { // Delete temp fields delete (field as any).useSameAnalyzerForSearch; - return sanitizeField(field); + return sanitizeField(runParametersSerializers(field)); }; export const fieldDeserializer: SerializerFunc = (field: Field): Field => { @@ -50,5 +73,5 @@ export const fieldDeserializer: SerializerFunc = (field: Field): Field => (field as any).useSameAnalyzerForSearch = {}.hasOwnProperty.call(field, 'search_analyzer') === false; - return field; + return runParametersDeserializers(field); }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts index 8ac1c2f8c35d1b..e99d8840d57dfe 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts @@ -16,6 +16,8 @@ export { OnFormUpdateArg, SerializerFunc, UseField, + UseArray, + ArrayItem, useForm, useFormContext, UseMultiFields, @@ -34,6 +36,7 @@ export { SuperSelectField, TextAreaField, TextField, + ComboBoxField, ToggleField, JsonEditorField, } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/components'; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts index b7bf4e6b112d37..55ec2fbcc99e65 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts @@ -99,6 +99,7 @@ export type ParameterName = | 'index_options_flattened' | 'index_options_keyword' | 'eager_global_ordinals' + | 'eager_global_ordinals_join' | 'index_prefixes' | 'index_phrases' | 'norms' @@ -120,6 +121,7 @@ export type ParameterName = | 'path' | 'dims' | 'depth_limit' + | 'relations' | 'max_shingle_size'; export interface Parameter { diff --git a/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts b/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts index 036388452f8763..d705dfa94adcac 100644 --- a/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts +++ b/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts @@ -177,6 +177,10 @@ class DocumentationService { return `${this.esDocsBase}/index-options.html`; } + public getJoinMultiLevelsPerformanceLink() { + return `${this.esDocsBase}/parent-join.html#_parent_join_and_performance`; + } + public getWellKnownTextLink() { return 'http://docs.opengeospatial.org/is/12-063r5/12-063r5.html'; } From ed04b17e0ea34f44b779bf26c233c4545c39fa55 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Wed, 22 Jan 2020 12:41:33 +0300 Subject: [PATCH 50/59] Move test utils inside the input_control_vis (#55169) * Move test utils to an appropriate folder * Change imports Co-authored-by: Elastic Machine --- .../components/editor/controls_tab.test.tsx | 3 +-- .../editor/list_control_editor.test.tsx | 5 ++-- .../editor/range_control_editor.test.tsx | 5 ++-- .../control/list_control_factory.test.ts | 3 +-- .../control/range_control_factory.test.ts | 3 +-- .../get_deps_mock.tsx | 2 +- .../get_index_pattern_mock.ts | 2 +- .../get_index_patterns_mock.ts | 0 .../get_search_service_mock.ts | 2 +- .../public/test_utils/index.ts | 24 +++++++++++++++++++ .../update_component.ts | 0 11 files changed, 34 insertions(+), 15 deletions(-) rename src/legacy/core_plugins/input_control_vis/public/{components/editor/__tests__ => test_utils}/get_deps_mock.tsx (97%) rename src/legacy/core_plugins/input_control_vis/public/{components/editor/__tests__ => test_utils}/get_index_pattern_mock.ts (94%) rename src/legacy/core_plugins/input_control_vis/public/{components/editor/__tests__ => test_utils}/get_index_patterns_mock.ts (100%) rename src/legacy/core_plugins/input_control_vis/public/{components/editor/__tests__ => test_utils}/get_search_service_mock.ts (96%) create mode 100644 src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts rename src/legacy/core_plugins/input_control_vis/public/{components/editor/__tests__ => test_utils}/update_component.ts (100%) diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index 4e7c9bafbf5102..3419d773bd09e6 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -21,8 +21,7 @@ import React from 'react'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { getDepsMock, getIndexPatternMock } from '../../test_utils'; import { ControlsTab, ControlsTabUiProps } from './controls_tab'; const indexPatternsMock = { diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx index f08a68b0da179b..942517e24514ed 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx @@ -25,11 +25,10 @@ import { shallow } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { getIndexPatternMock } from '../../test_utils/get_index_pattern_mock'; import { ListControlEditor } from './list_control_editor'; import { ControlParams } from '../../editor_utils'; -import { updateComponent } from './__tests__/update_component'; +import { getDepsMock, updateComponent } from '../../test_utils'; const controlParamsBase: ControlParams = { id: '1', diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index 55c4c71ce430b2..8f564e09bf4b2e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -24,12 +24,11 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { RangeControlEditor } from './range_control_editor'; import { ControlParams } from '../../editor_utils'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { updateComponent } from './__tests__/update_component'; +import { getDepsMock } from '../../test_utils/get_deps_mock'; +import { getIndexPatternMock, updateComponent } from '../../test_utils'; const controlParams: ControlParams = { id: '1', diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts index 2420907727638c..e6426e5a4c69d9 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts @@ -19,8 +19,7 @@ import { listControlFactory, ListControl } from './list_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; -import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; +import { getDepsMock, getSearchSourceMock } from '../test_utils'; const MockSearchSource = getSearchSourceMock(); const deps = getDepsMock(); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts index 5328aeb6c6a477..32df9de8ac9830 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts @@ -19,8 +19,7 @@ import { rangeControlFactory } from './range_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; -import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; +import { getDepsMock, getSearchSourceMock } from '../test_utils'; const deps = getDepsMock(); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx similarity index 97% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx index a9505aefa3113c..7b10eea3dde444 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { FieldList } from 'src/plugins/data/public'; -import { InputControlVisDependencies } from '../../../plugin'; +import { InputControlVisDependencies } from '../plugin'; const fields: FieldList = [] as any; fields.push({ name: 'myField' } as any); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts similarity index 94% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts index 638dd7170cb8df..da4663dc33429b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../../../../../../plugins/data/public'; +import { IIndexPattern } from 'src/plugins/data/public'; /** * Returns forced **Partial** IndexPattern for use in tests diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_patterns_mock.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_patterns_mock.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts similarity index 96% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts index 9da47bedcc7843..94a460086e9da7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SearchSource } from '../../../legacy_imports'; +import { SearchSource } from '../legacy_imports'; export const getSearchSourceMock = (esSearchResponse?: any): SearchSource => jest.fn().mockImplementation(() => ({ diff --git a/src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts new file mode 100644 index 00000000000000..b474d39a540742 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { getDepsMock } from './get_deps_mock'; +export { getIndexPatternMock } from './get_index_pattern_mock'; +export { getIndexPatternsMock } from './get_index_patterns_mock'; +export { getSearchSourceMock } from './get_search_service_mock'; +export { updateComponent } from './update_component'; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/update_component.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/update_component.ts From c6f9eff9f68eb771b7c8730fe543e6d9ac386f9c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 22 Jan 2020 10:55:04 +0100 Subject: [PATCH 51/59] [Uptime] Ping List Disable expand row if no body present (#54898) * update ping list * update snap * updated body * update snaps * fix i18n * updated translation * updated tests --- .../__snapshots__/doc_link_body.test.tsx.snap | 25 ++ .../__snapshots__/expanded_row.test.tsx.snap | 273 ++++++++++++++---- .../__tests__/doc_link_body.test.tsx | 15 + .../ping_list/__tests__/expanded_row.test.tsx | 25 +- .../functional/ping_list/doc_link_body.tsx | 36 +++ .../functional/ping_list/expanded_row.tsx | 32 +- .../functional/ping_list/ping_list.tsx | 92 +++--- 7 files changed, 388 insertions(+), 110 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap create mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap new file mode 100644 index 00000000000000..cfdbb5184d1707 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PingListExpandedRow renders expected elements for valid props 1`] = ` + + + docs +   + + , + } + } + /> + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap index 9fdca0d5b99f16..9aed8780682ee0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap @@ -1,77 +1,228 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PingListExpandedRow doesn't render list items if the body field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow doesn't render list items if the http field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow doesn't render list items if the response field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow renders error information when an error field is present 1`] = ` - - Forbidden - , - "title": "Error", - }, - Object { - "description": - The Title", - "hash": "testhash", - } - } - /> - - , - "title": "Response Body", - }, - ] - } -/> + + + + + Forbidden + , + "title": "Error", + }, + Object { + "description": + The Title", + "hash": "testhash", + } + } + /> + + + , + "title": "Response Body", + }, + ] + } + /> + + + `; exports[`PingListExpandedRow renders expected elements for valid props 1`] = ` - - The Title", - "hash": "testhash", - } - } - /> - - , - "title": "Response Body", - }, - ] - } -/> + + + + + The Title", + "hash": "testhash", + } + } + /> + + + , + "title": "Response Body", + }, + ] + } + /> + + + +`; + +exports[`PingListExpandedRow renders link to docs if body is not recorded but it is present 1`] = ` +
+
+
+
+ +
+
+
+
+ Response Body +
+
+
+ Body size is 1MB. +
+
+
+ Body not recorded. Read our + + docs  + + + for more information on recording response bodies. +
+
+
+
+
+
+
+`; + +exports[`PingListExpandedRow shallow renders link to docs if body is not recorded but it is present 1`] = ` + + + + + + + + , + "title": "Response Body", + }, + ] + } + /> + + + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx new file mode 100644 index 00000000000000..a29f647a226139 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { DocLinkForBody } from '../doc_link_body'; + +describe('PingListExpandedRow', () => { + it('renders expected elements for valid props', () => { + expect(shallowWithIntl()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx index f8914b1d46484a..9dbe48ec5553a0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { PingListExpandedRowComponent } from '../expanded_row'; import { Ping } from '../../../../../common/graphql/types'; +import { DocLinkForBody } from '../doc_link_body'; describe('PingListExpandedRow', () => { let ping: Ping; @@ -56,4 +57,26 @@ describe('PingListExpandedRow', () => { delete ping.http; expect(shallowWithIntl()).toMatchSnapshot(); }); + + it(`shallow renders link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + expect(shallowWithIntl()).toMatchSnapshot(); + }); + + it(`renders link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + expect(renderWithIntl()).toMatchSnapshot(); + }); + + it(`mount component to find link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + const component = mountWithIntl(); + + const docLinkComponent = component.find(DocLinkForBody); + + expect(docLinkComponent).toHaveLength(1); + }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx new file mode 100644 index 00000000000000..9640529f391c03 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiText } from '@elastic/eui'; + +const bodyDocsLink = + 'https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-heartbeat-options.html#monitor-http-response'; + +export const DocLinkForBody = () => { + const docsLink = ( + + {i18n.translate('xpack.uptime.pingList.drawer.body.docsLink', { + defaultMessage: 'docs', + description: 'Docs link to set response body', + })} +   + + + ); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx index 34aef698ee5df4..c684235122e34b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx @@ -5,10 +5,19 @@ */ // @ts-ignore formatNumber import { formatNumber } from '@elastic/eui/lib/services/format'; -import { EuiCodeBlock, EuiDescriptionList, EuiText } from '@elastic/eui'; -import React, { Fragment } from 'react'; +import { + EuiCallOut, + EuiCodeBlock, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { Ping, HttpBody } from '../../../../common/graphql/types'; +import { DocLinkForBody } from './doc_link_body'; interface Props { ping: Ping; @@ -52,7 +61,7 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => { } // Show the body, if present - if (ping.http && ping.http.response && ping.http.response.body) { + if (ping.http?.response?.body) { const body = ping.http.response.body; listItems.push({ @@ -60,12 +69,21 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => { defaultMessage: 'Response Body', }), description: ( - + <> - - + + {body.content ? : } + ), }); } - return ; + return ( + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 85517144edfc60..e8825dacc0078f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -115,6 +115,14 @@ export const PingListComponent = ({ }) ); + const pings: Ping[] = data?.allPings?.pings ?? []; + + const hasStatus: boolean = pings.reduce( + (hasHttpStatus: boolean, currentPing: Ping) => + hasHttpStatus || !!currentPing.http?.response?.status_code, + false + ); + const columns: any[] = [ { field: 'monitor.status', @@ -172,55 +180,57 @@ export const PingListComponent = ({ }), }, { - align: 'right', + align: hasStatus ? 'right' : 'center', field: 'error.type', name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', { defaultMessage: 'Error type', }), render: (error: string) => error ?? '-', }, - ]; - const pings: Ping[] = data?.allPings?.pings ?? []; - const hasStatus: boolean = pings.reduce( - (hasHttpStatus: boolean, currentPing: Ping) => - hasHttpStatus || !!currentPing.http?.response?.status_code, - false - ); - if (hasStatus) { - columns.push({ - field: 'http.response.status_code', + // Only add this column is there is any status present in list + ...(hasStatus + ? [ + { + field: 'http.response.status_code', + align: 'right', + name: ( + + {i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { + defaultMessage: 'Response code', + })} + + ), + render: (statusCode: string) => ( + + {statusCode} + + ), + }, + ] + : []), + { align: 'right', - name: ( - - {i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { - defaultMessage: 'Response code', - })} - - ), - render: (statusCode: string) => ( - - {statusCode}{' '} - - ), - }); - } + width: '24px', + isExpander: true, + render: (item: Ping) => { + return ( + toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} + disabled={!item.error && !(item.http?.response?.body?.bytes > 0)} + aria-label={ + itemIdToExpandedRowMap[item.id] + ? i18n.translate('xpack.uptime.pingList.collapseRow', { + defaultMessage: 'Collapse', + }) + : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) + } + iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + /> + ); + }, + }, + ]; - columns.push({ - align: 'right', - width: '24px', - isExpander: true, - render: (item: Ping) => ( - toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} - aria-label={ - itemIdToExpandedRowMap[item.id] - ? i18n.translate('xpack.uptime.pingList.collapseRow', { defaultMessage: 'Collapse' }) - : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) - } - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} - /> - ), - }); const pagination: Pagination = { initialPageSize: 20, pageIndex: 0, From 3e69ea5f01781d9658045169070c15c467e77ca7 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 22 Jan 2020 13:34:35 +0100 Subject: [PATCH 52/59] [NP] KibanaRequest provides request abortion event (#55061) * add aborted$ observable to KibanaRequest * complete observable on request end * update docs * update test suit names * always finish subscription * address comments --- ...bana-plugin-server.kibanarequest.events.md | 13 ++ .../kibana-plugin-server.kibanarequest.md | 3 +- ...bana-plugin-server.kibanarequest.socket.md | 2 + ...gin-server.kibanarequestevents.aborted_.md | 13 ++ ...ibana-plugin-server.kibanarequestevents.md | 20 +++ .../core/server/kibana-plugin-server.md | 1 + src/core/server/http/index.ts | 1 + .../http/integration_tests/request.test.ts | 127 ++++++++++++++++++ src/core/server/http/router/index.ts | 1 + src/core/server/http/router/request.ts | 32 ++++- src/core/server/index.ts | 1 + src/core/server/server.api.md | 6 + 12 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.kibanarequest.events.md create mode 100644 docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md create mode 100644 docs/development/core/server/kibana-plugin-server.kibanarequestevents.md create mode 100644 src/core/server/http/integration_tests/request.test.ts diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md new file mode 100644 index 00000000000000..5a002fc28f5dbb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [events](./kibana-plugin-server.kibanarequest.events.md) + +## KibanaRequest.events property + +Request events [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) + +Signature: + +```typescript +readonly events: KibanaRequestEvents; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index bc805fdc0b86f1..6603de24494d5a 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -23,10 +23,11 @@ export declare class KibanaRequestBody | | +| [events](./kibana-plugin-server.kibanarequest.events.md) | | KibanaRequestEvents | Request events [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) | | [headers](./kibana-plugin-server.kibanarequest.headers.md) | | Headers | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | | [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | -| [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | | +| [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | | [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md index 3880428273ac9c..c55f4656c993c2 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md @@ -4,6 +4,8 @@ ## KibanaRequest.socket property +[IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md new file mode 100644 index 00000000000000..d292d5d60bf5f7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) > [aborted$](./kibana-plugin-server.kibanarequestevents.aborted_.md) + +## KibanaRequestEvents.aborted$ property + +Observable that emits once if and when the request has been aborted. + +Signature: + +```typescript +aborted$: Observable; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md new file mode 100644 index 00000000000000..9137c4673a60ce --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) + +## KibanaRequestEvents interface + +Request events. + +Signature: + +```typescript +export interface KibanaRequestEvents +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aborted$](./kibana-plugin-server.kibanarequestevents.aborted_.md) | Observable<void> | Observable that emits once if and when the request has been aborted. | + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 00ab83123319ac..cd469fe6a98c2a 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -76,6 +76,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | | [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md) | | | [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | +| [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) | Request events. | | [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | | [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | | [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 55ba813484268e..d31afe1670e419 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -29,6 +29,7 @@ export { HttpResponsePayload, ErrorHttpResponseOptions, KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, IKibanaResponse, diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts new file mode 100644 index 00000000000000..bc1bbc881315ab --- /dev/null +++ b/src/core/server/http/integration_tests/request.test.ts @@ -0,0 +1,127 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import supertest from 'supertest'; + +import { HttpService } from '../http_service'; + +import { contextServiceMock } from '../../context/context_service.mock'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { createHttpServer } from '../test_utils'; + +let server: HttpService; + +let logger: ReturnType; +const contextSetup = contextServiceMock.createSetupContract(); + +const setupDeps = { + context: contextSetup, +}; + +beforeEach(() => { + logger = loggingServiceMock.create(); + + server = createHttpServer({ logger }); +}); + +afterEach(async () => { + await server.stop(); +}); + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +describe('KibanaRequest', () => { + describe('events', () => { + describe('aborted$', () => { + it('emits once and completes when request aborted', async done => { + expect.assertions(1); + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: () => { + expect(nextSpy).toHaveBeenCalledTimes(1); + done(); + }, + }); + + // prevents the server to respond + await delay(30000); + return res.ok({ body: 'ok' }); + }); + + await server.start(); + + const incomingRequest = supertest(innerServer.listener) + .get('/') + // end required to send request + .end(); + + setTimeout(() => incomingRequest.abort(), 50); + }); + + it('completes & does not emit when request handled', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + const completeSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: completeSpy, + }); + + return res.ok({ body: 'ok' }); + }); + + await server.start(); + + await supertest(innerServer.listener).get('/'); + + expect(nextSpy).toHaveBeenCalledTimes(0); + expect(completeSpy).toHaveBeenCalledTimes(1); + }); + + it('completes & does not emit when request rejected', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + const completeSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: completeSpy, + }); + + return res.badRequest(); + }); + + await server.start(); + + await supertest(innerServer.listener).get('/'); + + expect(nextSpy).toHaveBeenCalledTimes(0); + expect(completeSpy).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index 084d30d6944744..32663d1513f36b 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -21,6 +21,7 @@ export { Headers, filterHeaders, ResponseHeaders, KnownHeaders } from './headers export { Router, RequestHandler, IRouter, RouteRegistrar } from './router'; export { KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, isRealRequest, diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 47b001700b0154..22fb2d2643d1c7 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -19,6 +19,8 @@ import { Url } from 'url'; import { Request } from 'hapi'; +import { Observable, fromEvent, merge } from 'rxjs'; +import { shareReplay, first, takeUntil } from 'rxjs/operators'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { Headers } from './headers'; @@ -46,6 +48,17 @@ export interface KibanaRequestRoute { options: KibanaRequestRouteOptions; } +/** + * Request events. + * @public + * */ +export interface KibanaRequestEvents { + /** + * Observable that emits once if and when the request has been aborted. + */ + aborted$: Observable; +} + /** * @deprecated * `hapi` request object, supported during migration process only for backward compatibility. @@ -115,7 +128,10 @@ export class KibanaRequest< */ public readonly headers: Headers; + /** {@link IKibanaSocket} */ public readonly socket: IKibanaSocket; + /** Request events {@link KibanaRequestEvents} */ + public readonly events: KibanaRequestEvents; /** @internal */ protected readonly [requestSymbol]: Request; @@ -138,12 +154,22 @@ export class KibanaRequest< enumerable: false, }); - this.route = deepFreeze(this.getRouteInfo()); + this.route = deepFreeze(this.getRouteInfo(request)); this.socket = new KibanaSocket(request.raw.req.socket); + this.events = this.getEvents(request); + } + + private getEvents(request: Request): KibanaRequestEvents { + const finish$ = merge( + fromEvent(request.raw.req, 'end'), // all data consumed + fromEvent(request.raw.req, 'close') // connection was closed + ).pipe(shareReplay(1), first()); + return { + aborted$: fromEvent(request.raw.req, 'aborted').pipe(first(), takeUntil(finish$)), + } as const; } - private getRouteInfo(): KibanaRequestRoute { - const request = this[requestSymbol]; + private getRouteInfo(request: Request): KibanaRequestRoute { const method = request.method as Method; const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 3f67b9a656bb79..a97d2970dca881 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -109,6 +109,7 @@ export { IKibanaSocket, IsAuthenticated, KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, IKibanaResponse, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6e41a4aefba302..dce5ec64bfa663 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -879,6 +879,7 @@ export class KibanaRequest; +} + // @public export interface KibanaRequestRoute { // (undocumented) From e828a1295492659c446d4dc894e46bba40b35c68 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 22 Jan 2020 07:50:32 -0500 Subject: [PATCH 53/59] [SIEM] [Detection Engine] Log time gaps as failures for now (#55515) * log a failure to failure history if time gap is detected. stop-gap solution until a feature is fully fleshed out to report this and future messaging / monitoring. * write date the gap warning occurred in the last_failure_at field, along with the status_date field. --- .../signals/signal_rule_alert_type.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index b19e4f48fdb3e4..143fad602daea0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -134,6 +134,27 @@ export const signalRulesAlertType = ({ logger.warn( `Signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" has a time gap of ${gap.humanize()} (${gap.asMilliseconds()}ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.` ); + // write a failure status whenever we have a time gap + // this is a temporary solution until general activity + // monitoring is developed as a feature + const gapDate = new Date().toISOString(); + await services.savedObjectsClient.create(ruleStatusSavedObjectType, { + alertId, + statusDate: gapDate, + status: 'failed', + lastFailureAt: gapDate, + lastSuccessAt: currentStatusSavedObject.attributes.lastSuccessAt, + lastFailureMessage: `Signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" has a time gap of ${gap.humanize()} (${gap.asMilliseconds()}ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.`, + lastSuccessMessage: currentStatusSavedObject.attributes.lastSuccessMessage, + }); + + if (ruleStatusSavedObjects.saved_objects.length >= 6) { + // delete fifth status and prepare to insert a newer one. + const toDelete = ruleStatusSavedObjects.saved_objects.slice(5); + await toDelete.forEach(async item => + services.savedObjectsClient.delete(ruleStatusSavedObjectType, item.id) + ); + } } // set searchAfter page size to be the lesser of default page size or maxSignals. const searchAfterSize = From d23d8f0afd36ac97fdf944f8a885ec0605556c46 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 22 Jan 2020 13:52:53 +0100 Subject: [PATCH 54/59] Restore 200 OK + response payload for save and delete endpoints (#55535) --- .../np_ready/routes/api/watch/register_delete_route.ts | 5 +++-- .../server/np_ready/routes/api/watch/register_save_route.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts index 3402cc283dba04..26b6f96f6cb8cc 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts @@ -24,8 +24,9 @@ export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) const { watchId } = request.params; try { - await deleteWatch(callWithRequest, watchId); - return response.noContent(); + return response.ok({ + body: await deleteWatch(callWithRequest, watchId), + }); } catch (e) { // Case: Error from Elasticsearch JS client if (isEsError(e)) { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts index 5d22392d49ed8a..df4117dee2bfda 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts @@ -76,8 +76,9 @@ export function registerSaveRoute(deps: RouteDependencies, legacy: ServerShim) { try { // Create new watch - await saveWatch(callWithRequest, id, serializedWatch); - return response.noContent(); + return response.ok({ + body: await saveWatch(callWithRequest, id, serializedWatch), + }); } catch (e) { // Case: Error from Elasticsearch JS client if (isEsError(e)) { From b8d35105c715b9ba85e9c51268a4185c07a84594 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 22 Jan 2020 13:58:18 +0100 Subject: [PATCH 55/59] [SearchProfiler] Move out of legacy (#55331) * Initial move of searchprofiler into new platform directory, lots of things need testing * Whitespace, clean up types and remove unused files * First iteration of end-to-end plugin working - Updated license check to only check for presence of basic license (not search profiler as a feature - Updated the payload: removed types from validation - Also added README in public regarding the location of styles * Added extractProfilerErrorMessage function to interface with new error reporting from profiler endpoint * Fix paths to test_utils * Update I18n for search profiler * Fix react hooks ordering bug with license status updates and fix test (wait for first license object before rendering) * Added index.ts file to common in searchprofiler route Marked types and values as internal Removed unnecessary "async" from function Update import to not use "src" alias Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 2 +- x-pack/.i18nrc.json | 4 +- .../legacy/plugins/searchprofiler/README.md | 26 ------ x-pack/legacy/plugins/searchprofiler/index.ts | 47 ++--------- .../{np_ready/application => }/index.scss | 0 .../plugins/searchprofiler/public/legacy.ts | 28 ------- .../searchprofiler/public/np_ready/plugin.ts | 62 -------------- .../searchprofiler/public/register_feature.js | 28 ------- .../application => }/styles/_index.scss | 0 .../application => }/styles/_mixins.scss | 0 .../components/_highlight_details_flyout.scss | 0 .../styles/components/_percentage_badge.scss | 0 .../styles/components/_profile_tree.scss | 0 .../styles/containers/_main.scss | 0 .../containers/_profile_query_editor.scss | 0 .../server/np_ready/lib/check_license.test.ts | 81 ------------------- .../server/np_ready/lib/check_license.ts | 59 -------------- .../searchprofiler/server/np_ready/plugin.ts | 38 --------- .../server/np_ready/routes/profile.ts | 53 ------------ .../searchprofiler/server/np_ready/types.ts | 33 -------- .../searchprofiler/common/constants.ts | 14 ++++ .../searchprofiler/common}/index.ts | 4 +- .../searchprofiler/common/types.ts} | 9 ++- x-pack/plugins/searchprofiler/kibana.json | 8 ++ .../plugins/searchprofiler/public/README.md | 3 + .../public}/application/boot.tsx | 7 +- .../highlight_details_flyout.test.tsx | 2 +- .../highlight_details_flyout.tsx | 0 .../highlight_details_table.tsx | 0 .../highlight_details_flyout/index.ts | 0 .../public}/application/components/index.ts | 0 .../components/license_warning_notice.test.ts | 2 +- .../components/license_warning_notice.tsx | 2 +- .../components/percentage_badge.tsx | 0 .../__tests__/fixtures/breakdown.ts | 6 ++ .../__tests__/fixtures/normalize_indices.ts | 0 .../__tests__/fixtures/normalize_times.ts | 6 ++ .../fixtures/processed_search_response.ts | 0 .../__tests__/fixtures/search_response.ts | 0 .../profile_tree/__tests__/init_data.test.ts | 0 .../__tests__/profile_tree.test.tsx | 2 +- .../__tests__/unsafe_utils.test.ts | 0 .../profile_tree/__tests__/utils.test.ts | 0 .../components/profile_tree/constants.ts | 0 .../profile_tree/highlight_context.tsx | 0 .../components/profile_tree/index.ts | 0 .../components/profile_tree/index_details.tsx | 0 .../components/profile_tree/init_data.ts | 0 .../components/profile_tree/profile_tree.tsx | 0 .../profile_tree/shard_details/index.ts | 0 .../shard_details/shard_details.tsx | 0 .../shard_details/shard_details_tree.tsx | 0 .../shard_details/shard_details_tree_node.tsx | 0 .../components/profile_tree/types.ts | 0 .../components/profile_tree/unsafe_utils.ts | 0 .../profile_tree/use_highlight_tree_node.ts | 0 .../components/profile_tree/utils.ts | 0 .../components/searchprofiler_tabs.test.ts | 2 +- .../components/searchprofiler_tabs.tsx | 0 .../public}/application/containers/index.ts | 0 .../empty_tree_placeholder.test.tsx | 2 +- .../components/empty_tree_placeholder.tsx | 0 .../containers/main/components/index.ts | 0 .../profile_loading_placeholder.test.tsx | 2 +- .../profile_loading_placeholder.tsx | 0 .../application/containers/main/index.ts | 0 .../application/containers/main/main.tsx | 6 +- .../containers/profile_query_editor.tsx | 4 +- .../application/contexts/app_context.tsx | 31 +++++-- .../application/contexts/profiler_context.tsx | 0 .../application/editor/editor.test.tsx | 2 +- .../public}/application/editor/editor.tsx | 13 +-- .../public}/application/editor/index.ts | 0 .../public}/application/editor/init_editor.ts | 0 .../editor/use_ui_ace_keyboard_mode.tsx | 0 .../application/editor/worker/index.ts | 0 .../application/editor/worker/worker.d.ts | 0 .../application/editor/worker/worker.js | 0 .../editor/x_json_highlight_rules.ts | 0 .../public}/application/editor/x_json_mode.ts | 0 .../public}/application/hooks/index.ts | 0 .../application/hooks/use_request_profile.ts | 30 +++++-- .../public}/application/index.tsx | 10 +-- .../public}/application/store/index.ts | 0 .../public}/application/store/reducer.test.ts | 0 .../public}/application/store/reducer.ts | 0 .../public}/application/store/store.ts | 0 .../public}/application/types.ts | 0 .../utils/check_for_json_errors.test.ts | 0 .../utils/check_for_json_errors.ts | 0 .../public}/application/utils/index.ts | 0 .../public}/application/utils/ms_to_pretty.ts | 0 .../application/utils/ns_to_pretty.test.ts | 0 .../public}/application/utils/ns_to_pretty.ts | 0 .../searchprofiler/public}/index.ts | 0 .../plugins/searchprofiler/public/plugin.ts | 69 ++++++++++++++++ x-pack/plugins/searchprofiler/public/types.ts | 15 ++++ x-pack/plugins/searchprofiler/server/index.ts | 11 +++ .../plugins/searchprofiler/server/plugin.ts | 59 ++++++++++++++ .../searchprofiler/server/routes/profile.ts | 70 ++++++++++++++++ x-pack/plugins/searchprofiler/server/types.ts | 22 +++++ .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../apps/dev_tools/searchprofiler_editor.ts | 2 +- 104 files changed, 372 insertions(+), 510 deletions(-) delete mode 100644 x-pack/legacy/plugins/searchprofiler/README.md rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/index.scss (100%) delete mode 100644 x-pack/legacy/plugins/searchprofiler/public/legacy.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/public/register_feature.js rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/_index.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/_mixins.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/components/_highlight_details_flyout.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/components/_percentage_badge.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/components/_profile_tree.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/containers/_main.scss (100%) rename x-pack/legacy/plugins/searchprofiler/public/{np_ready/application => }/styles/containers/_profile_query_editor.scss (100%) delete mode 100644 x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts delete mode 100644 x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts create mode 100644 x-pack/plugins/searchprofiler/common/constants.ts rename x-pack/{legacy/plugins/searchprofiler/server/np_ready/lib => plugins/searchprofiler/common}/index.ts (75%) rename x-pack/{legacy/plugins/searchprofiler/server/np_ready/index.ts => plugins/searchprofiler/common/types.ts} (64%) create mode 100644 x-pack/plugins/searchprofiler/kibana.json create mode 100644 x-pack/plugins/searchprofiler/public/README.md rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/boot.tsx (82%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx (94%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/highlight_details_flyout/highlight_details_flyout.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/highlight_details_flyout/highlight_details_table.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/highlight_details_flyout/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/license_warning_notice.test.ts (87%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/license_warning_notice.tsx (99%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/percentage_badge.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/fixtures/breakdown.ts (88%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/fixtures/normalize_indices.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/fixtures/normalize_times.ts (98%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/fixtures/processed_search_response.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/fixtures/search_response.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/init_data.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/profile_tree.test.tsx (89%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/unsafe_utils.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/__tests__/utils.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/constants.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/highlight_context.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/index_details.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/init_data.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/profile_tree.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/shard_details/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/shard_details/shard_details.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/shard_details/shard_details_tree.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/shard_details/shard_details_tree_node.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/types.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/unsafe_utils.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/use_highlight_tree_node.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/profile_tree/utils.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/searchprofiler_tabs.test.ts (90%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/components/searchprofiler_tabs.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/components/empty_tree_placeholder.test.tsx (85%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/components/empty_tree_placeholder.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/components/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/components/profile_loading_placeholder.test.tsx (86%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/components/profile_loading_placeholder.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/main/main.tsx (96%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/containers/profile_query_editor.tsx (97%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/contexts/app_context.tsx (53%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/contexts/profiler_context.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/editor.test.tsx (91%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/editor.tsx (84%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/init_editor.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/use_ui_ace_keyboard_mode.tsx (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/worker/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/worker/worker.d.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/worker/worker.js (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/x_json_highlight_rules.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/editor/x_json_mode.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/hooks/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/hooks/use_request_profile.ts (73%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/index.tsx (74%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/store/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/store/reducer.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/store/reducer.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/store/store.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/types.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/check_for_json_errors.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/check_for_json_errors.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/index.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/ms_to_pretty.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/ns_to_pretty.test.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/application/utils/ns_to_pretty.ts (100%) rename x-pack/{legacy/plugins/searchprofiler/public/np_ready => plugins/searchprofiler/public}/index.ts (100%) create mode 100644 x-pack/plugins/searchprofiler/public/plugin.ts create mode 100644 x-pack/plugins/searchprofiler/public/types.ts create mode 100644 x-pack/plugins/searchprofiler/server/index.ts create mode 100644 x-pack/plugins/searchprofiler/server/plugin.ts create mode 100644 x-pack/plugins/searchprofiler/server/routes/profile.ts create mode 100644 x-pack/plugins/searchprofiler/server/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1fcfd76b22b40d..547776c4cfe661 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -151,6 +151,6 @@ /x-pack/legacy/plugins/license_management/ @elastic/es-ui /x-pack/legacy/plugins/remote_clusters/ @elastic/es-ui /x-pack/legacy/plugins/rollup/ @elastic/es-ui -/x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui +/x-pack/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 71e3bdd6c8c846..869a5115a8aee0 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -28,9 +28,9 @@ "xpack.ml": "legacy/plugins/ml", "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "legacy/plugins/remote_clusters", - "xpack.reporting": [ "plugins/reporting", "legacy/plugins/reporting" ], + "xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"], "xpack.rollupJobs": "legacy/plugins/rollup", - "xpack.searchProfiler": "legacy/plugins/searchprofiler", + "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": ["legacy/plugins/security", "plugins/security"], "xpack.server": "legacy/server", "xpack.siem": "legacy/plugins/siem", diff --git a/x-pack/legacy/plugins/searchprofiler/README.md b/x-pack/legacy/plugins/searchprofiler/README.md deleted file mode 100644 index 1dec1bb4e54504..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Profiler - -A UI for the query and aggregation profiler in Elasticsearch - -## Development - -Assuming you've checked out x-plugins next to kibana... - -- Run `yarn kbn bootstrap` -- Run `yarn start` to watch for and sync files on change -- Open a new terminal to run Kibana - use `yarn start` to launch it in dev mode - - Kibana will automatically restart as files are synced - - If you need debugging output, run `DEBUG=reporting yarn start` instead - -If you have installed this somewhere other than via x-plugins, and next to the kibana repo, you'll need to change the `pathToKibana` setting in `gulpfile.js` - -## Testing - -To run the server tests, change into `x-plugins/kibana` and run: - -```bash -mocha --debug --compilers js:@babel/register plugins/profiler/**/__tests__/**/*.js -``` - - ---kbnServer.tests_bundle.pluginId diff --git a/x-pack/legacy/plugins/searchprofiler/index.ts b/x-pack/legacy/plugins/searchprofiler/index.ts index 834f331cd7bf41..fab2e438473488 100644 --- a/x-pack/legacy/plugins/searchprofiler/index.ts +++ b/x-pack/legacy/plugins/searchprofiler/index.ts @@ -5,12 +5,10 @@ */ import { resolve } from 'path'; -import Boom from 'boom'; -import { CoreSetup } from 'src/core/server'; -import { Server } from 'src/legacy/server/kbn_server'; -import { LegacySetup } from './server/np_ready/types'; -import { plugin } from './server/np_ready'; +// TODO: +// Until we can process SCSS in new platform, this part of Searchprofiler +// legacy must remain here. export const searchprofiler = (kibana: any) => { const publicSrc = resolve(__dirname, 'public'); @@ -22,43 +20,8 @@ export const searchprofiler = (kibana: any) => { publicDir: publicSrc, uiExports: { - // NP Ready - devTools: [`${publicSrc}/legacy`], - styleSheetPaths: `${publicSrc}/np_ready/application/index.scss`, - // Legacy - home: ['plugins/searchprofiler/register_feature'], - }, - init(server: Server) { - const serverPlugin = plugin(); - const thisPlugin = this; - - const commonRouteConfig = { - pre: [ - function forbidApiAccess() { - const licenseCheckResults = server.plugins.xpack_main.info - .feature(thisPlugin.id) - .getLicenseCheckResults(); - if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { - return null; - } else { - throw Boom.forbidden(licenseCheckResults.message); - } - }, - ], - }; - - const legacySetup: LegacySetup = { - route: (args: Parameters[0]) => server.route(args), - plugins: { - __LEGACY: { - thisPlugin, - xpackMain: server.plugins.xpack_main, - elasticsearch: server.plugins.elasticsearch, - commonRouteConfig, - }, - }, - }; - serverPlugin.setup({} as CoreSetup, legacySetup); + styleSheetPaths: `${publicSrc}/index.scss`, }, + init() {}, }); }; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.scss b/x-pack/legacy/plugins/searchprofiler/public/index.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.scss rename to x-pack/legacy/plugins/searchprofiler/public/index.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts b/x-pack/legacy/plugins/searchprofiler/public/legacy.ts deleted file mode 100644 index b767705e025a00..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { npSetup } from 'ui/new_platform'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -// @ts-ignore -import { formatAngularHttpError } from 'ui/notify/lib'; -import 'ui/autoload/all'; - -import { plugin } from './np_ready'; - -const pluginInstance = plugin({} as any); - -pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - I18nContext, - licenseEnabled: xpackInfo.get('features.searchprofiler.enableAppLink'), - notifications: npSetup.core.notifications.toasts, - formatAngularHttpError, - }, -}); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts deleted file mode 100644 index f2acc97e55a705..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { - Plugin, - CoreStart, - CoreSetup, - PluginInitializerContext, - ToastsStart, -} from 'src/core/public'; - -import { DevToolsSetup } from '../../../../../../src/plugins/dev_tools/public'; - -export class SearchProfilerUIPlugin implements Plugin { - constructor(ctx: PluginInitializerContext) {} - - async setup( - core: CoreSetup, - plugins: { - __LEGACY: { - I18nContext: any; - licenseEnabled: boolean; - notifications: ToastsStart; - formatAngularHttpError: any; - }; - dev_tools: DevToolsSetup; - } - ) { - const { http } = core; - const { - __LEGACY: { I18nContext, licenseEnabled, notifications, formatAngularHttpError }, - dev_tools, - } = plugins; - dev_tools.register({ - id: 'searchprofiler', - title: i18n.translate('xpack.searchProfiler.pageDisplayName', { - defaultMessage: 'Search Profiler', - }), - order: 5, - enableRouting: false, - async mount(ctx, params) { - const { boot } = await import('./application/boot'); - return boot({ - http, - licenseEnabled, - el: params.element, - I18nContext, - notifications, - formatAngularHttpError, - }); - }, - }); - } - - async start(core: CoreStart, plugins: any) {} - - async stop() {} -} diff --git a/x-pack/legacy/plugins/searchprofiler/public/register_feature.js b/x-pack/legacy/plugins/searchprofiler/public/register_feature.js deleted file mode 100644 index 2a4218e2527c7c..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/register_feature.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'searchprofiler', - title: i18n.translate('xpack.searchProfiler.registryProviderTitle', { - defaultMessage: 'Search Profiler', - }), - description: i18n.translate('xpack.searchProfiler.registryProviderDescription', { - defaultMessage: 'Quickly check the performance of any Elasticsearch query.', - }), - icon: 'searchProfilerApp', - path: '/app/kibana#/dev_tools/searchprofiler', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/_index.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/_index.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_mixins.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/_mixins.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_mixins.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/_mixins.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_highlight_details_flyout.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_highlight_details_flyout.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_highlight_details_flyout.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_highlight_details_flyout.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_percentage_badge.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_percentage_badge.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_percentage_badge.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_percentage_badge.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_profile_tree.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_profile_tree.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/containers/_main.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/containers/_main.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_profile_query_editor.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/containers/_profile_query_editor.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_profile_query_editor.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/containers/_profile_query_editor.scss diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts deleted file mode 100644 index 1b8155221cb9d1..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { set } from 'lodash'; -import { checkLicense } from './check_license'; - -describe('check_license', () => { - let mockLicenseInfo: any; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is > basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(true); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts deleted file mode 100644 index 2a22d615ca6e54..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info'; - -export function checkLicense( - xpackLicenseInfo: XPackInfo -): { showAppLink: boolean; enableAppLink: boolean; message: string | undefined } { - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - showAppLink: true, - enableAppLink: false, - message: i18n.translate('xpack.searchProfiler.unavailableLicenseInformationMessage', { - defaultMessage: - 'Search Profiler is unavailable - license information is not available at this time.', - }), - }; - } - - const isLicenseActive = xpackLicenseInfo.license.isActive(); - let message: string | undefined; - if (!isLicenseActive) { - message = i18n.translate('xpack.searchProfiler.licenseHasExpiredMessage', { - defaultMessage: 'Search Profiler is unavailable - license has expired.', - }); - } - - if ( - xpackLicenseInfo.license.isOneOf([ - 'trial', - 'basic', - 'standard', - 'gold', - 'platinum', - 'enterprise', - ]) - ) { - return { - showAppLink: true, - enableAppLink: isLicenseActive, - message, - }; - } - - message = i18n.translate('xpack.searchProfiler.upgradeLicenseMessage', { - defaultMessage: - 'Search Profiler is unavailable for the current {licenseInfo} license. Please upgrade your license.', - values: { licenseInfo: xpackLicenseInfo.license.getType() }, - }); - return { - showAppLink: false, - enableAppLink: false, - message, - }; -} diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts deleted file mode 100644 index e00e2829f980d2..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreSetup, Plugin } from 'src/core/server'; -import { LegacySetup } from './types'; -import { checkLicense } from './lib'; -// @ts-ignore -import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status'; - -import * as profileRoute from './routes/profile'; - -export class SearchProfilerServerPlugin implements Plugin { - async setup( - core: CoreSetup, - { - route, - plugins: { - __LEGACY: { thisPlugin, elasticsearch, xpackMain, commonRouteConfig }, - }, - }: LegacySetup - ) { - mirrorPluginStatus(xpackMain, thisPlugin); - (xpackMain as any).status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMain.info.feature(thisPlugin.id).registerLicenseCheckResultsGenerator(checkLicense); - }); - - profileRoute.register({ elasticsearch }, route, commonRouteConfig); - } - - async start() {} - - stop(): void {} -} diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts deleted file mode 100644 index 082307b5a7a2be..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import Joi from 'joi'; -import { RequestShim, ServerShim, RegisterRoute } from '../types'; - -export const handler = async (server: ServerShim, request: RequestShim) => { - const { callWithRequest } = server.elasticsearch.getCluster('data'); - let parsed = request.payload.query; - parsed.profile = true; - parsed = JSON.stringify(parsed, null, 2); - - const body = { - index: request.payload.index, - body: parsed, - }; - try { - const resp = await callWithRequest(request, 'search', body); - return { - ok: true, - resp, - }; - } catch (err) { - return { - ok: false, - err, - }; - } -}; - -export const register = (server: ServerShim, route: RegisterRoute, commonConfig: any) => { - route({ - path: '/api/searchprofiler/profile', - method: 'POST', - config: { - ...commonConfig, - validate: { - payload: Joi.object() - .keys({ - query: Joi.object().required(), - index: Joi.string().required(), - type: Joi.string().optional(), - }) - .required(), - }, - }, - handler: req => { - return handler(server, { headers: req.headers, payload: req.payload as any }); - }, - }); -}; diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts deleted file mode 100644 index 9b25f8bb36b0cf..00000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ServerRoute } from 'hapi'; -import { ElasticsearchPlugin, Request } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; - -export type RegisterRoute = (args: ServerRoute & { config: any }) => void; - -export interface LegacyPlugins { - __LEGACY: { - thisPlugin: any; - elasticsearch: ElasticsearchPlugin; - xpackMain: XPackMainPlugin; - commonRouteConfig: any; - }; -} - -export interface LegacySetup { - route: RegisterRoute; - plugins: LegacyPlugins; -} - -export interface ServerShim { - elasticsearch: ElasticsearchPlugin; -} - -export interface RequestShim extends Request { - payload: any; -} diff --git a/x-pack/plugins/searchprofiler/common/constants.ts b/x-pack/plugins/searchprofiler/common/constants.ts new file mode 100644 index 00000000000000..be653543f21f50 --- /dev/null +++ b/x-pack/plugins/searchprofiler/common/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +/** @internal */ +export const PLUGIN = Object.freeze({ + id: 'searchprofiler', + minimumLicenseType: basicLicense, +}); diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts b/x-pack/plugins/searchprofiler/common/index.ts similarity index 75% rename from x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts rename to x-pack/plugins/searchprofiler/common/index.ts index f2c070fd44b6e6..e345d99f4bed70 100644 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts +++ b/x-pack/plugins/searchprofiler/common/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; +export { PLUGIN } from './constants'; + +export { LicenseStatus } from './types'; diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/index.ts b/x-pack/plugins/searchprofiler/common/types.ts similarity index 64% rename from x-pack/legacy/plugins/searchprofiler/server/np_ready/index.ts rename to x-pack/plugins/searchprofiler/common/types.ts index 9253a0d6b15242..fa69f249feb46e 100644 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/index.ts +++ b/x-pack/plugins/searchprofiler/common/types.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SearchProfilerServerPlugin } from './plugin'; -export const plugin = () => { - return new SearchProfilerServerPlugin(); -}; +/** @internal */ +export interface LicenseStatus { + valid: boolean; + message?: string; +} diff --git a/x-pack/plugins/searchprofiler/kibana.json b/x-pack/plugins/searchprofiler/kibana.json new file mode 100644 index 00000000000000..af8ee68a9bfa29 --- /dev/null +++ b/x-pack/plugins/searchprofiler/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "searchprofiler", + "version": "8.0.0", + "kibanaVersion": "kibana", + "requiredPlugins": ["dev_tools", "home", "licensing"], + "server": true, + "ui": true +} diff --git a/x-pack/plugins/searchprofiler/public/README.md b/x-pack/plugins/searchprofiler/public/README.md new file mode 100644 index 00000000000000..3cf79162b3965f --- /dev/null +++ b/x-pack/plugins/searchprofiler/public/README.md @@ -0,0 +1,3 @@ +## Please note + +See x-pack/legacy/plugins/searchprofiler/public for styles. diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/boot.tsx b/x-pack/plugins/searchprofiler/public/application/boot.tsx similarity index 82% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/boot.tsx rename to x-pack/plugins/searchprofiler/public/application/boot.tsx index fa02124f8a2454..d6e865f0eb8865 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/boot.tsx +++ b/x-pack/plugins/searchprofiler/public/application/boot.tsx @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import { render, unmountComponentAtNode } from 'react-dom'; +import { HttpStart as Http, ToastsSetup } from 'kibana/public'; import React from 'react'; -import { HttpStart as Http, ToastsSetup } from 'src/core/public'; + +import { LicenseStatus } from '../../common'; import { App } from '.'; export interface Dependencies { el: HTMLElement; http: Http; - licenseEnabled: boolean; I18nContext: any; notifications: ToastsSetup; - formatAngularHttpError: any; + initialLicenseStatus: LicenseStatus; } export type AppDependencies = Omit; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx similarity index 94% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx rename to x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx index 30a4c1dce1dff7..f539baadd50525 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../../test_utils'; import { HighlightDetailsFlyout, Props } from '.'; describe('Highlight Details Flyout', () => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx rename to x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_table.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx rename to x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_table.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/index.ts b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/index.ts rename to x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/index.ts b/x-pack/plugins/searchprofiler/public/application/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/index.ts rename to x-pack/plugins/searchprofiler/public/application/components/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.test.ts b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.test.ts similarity index 87% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.test.ts rename to x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.test.ts index ebe7a00737868f..79a2d6c1426f72 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../test_utils'; import { LicenseWarningNotice } from './license_warning_notice'; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.tsx b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.tsx similarity index 99% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.tsx rename to x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.tsx index da9991529e7d44..295e15056acc4f 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/license_warning_notice.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice.tsx @@ -36,7 +36,7 @@ export const LicenseWarningNotice = () => { title={i18n.translate('xpack.searchProfiler.licenseErrorMessageTitle', { defaultMessage: 'License error', })} - color="warning" + color="danger" iconType="alert" style={{ padding: '16px' }} > diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx b/x-pack/plugins/searchprofiler/public/application/components/percentage_badge.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx rename to x-pack/plugins/searchprofiler/public/application/components/percentage_badge.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/breakdown.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/breakdown.ts similarity index 88% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/breakdown.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/breakdown.ts index a65af9a7a3ff3e..41cad70b6b1dd6 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/breakdown.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/breakdown.ts @@ -1,3 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + export const breakdown = { advance: 0, advance_count: 0, diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_indices.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/normalize_indices.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_indices.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/normalize_indices.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/normalize_times.ts similarity index 98% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/normalize_times.ts index f26de49138f121..3cbacd2d31ac2b 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/normalize_times.ts @@ -1,3 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + export const inputTimes = [ { type: 'BooleanQuery', diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/processed_search_response.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/processed_search_response.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/processed_search_response.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/processed_search_response.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/search_response.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/fixtures/search_response.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/init_data.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/init_data.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/profile_tree.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/profile_tree.test.tsx similarity index 89% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/profile_tree.test.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/profile_tree.test.tsx index ca95ac97e260a9..dbd30f584850c3 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/profile_tree.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/profile_tree.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../../../test_utils'; import { searchResponse } from './fixtures/search_response'; import { ProfileTree, Props } from '../profile_tree'; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/unsafe_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/unsafe_utils.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/utils.test.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/utils.test.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/__tests__/utils.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/constants.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/constants.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/constants.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/constants.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/highlight_context.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/highlight_context.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/highlight_context.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/highlight_context.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/index.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/index.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/index_details.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index_details.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/index_details.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/index_details.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/profile_tree.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/profile_tree.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/index.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/index.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details_tree.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details_tree.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details_tree_node.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/shard_details/shard_details_tree_node.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/types.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/types.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/types.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/types.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/unsafe_utils.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/use_highlight_tree_node.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/use_highlight_tree_node.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/use_highlight_tree_node.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/use_highlight_tree_node.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/utils.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/utils.ts rename to x-pack/plugins/searchprofiler/public/application/components/profile_tree/utils.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/searchprofiler_tabs.test.ts b/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs.test.ts similarity index 90% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/searchprofiler_tabs.test.ts rename to x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs.test.ts index 8ee43e28bcd2c5..28eb56ae051c7d 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/searchprofiler_tabs.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../test_utils'; import { SearchProfilerTabs, Props } from './searchprofiler_tabs'; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/searchprofiler_tabs.tsx b/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/searchprofiler_tabs.tsx rename to x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/index.ts b/x-pack/plugins/searchprofiler/public/application/containers/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/index.ts rename to x-pack/plugins/searchprofiler/public/application/containers/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.test.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/components/empty_tree_placeholder.test.tsx similarity index 85% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.test.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/main/components/empty_tree_placeholder.test.tsx index 4f17d0b4304f64..811f83041006bb 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/main/components/empty_tree_placeholder.test.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../../../test_utils'; import { EmptyTreePlaceHolder } from '.'; describe('EmptyTreePlaceholder', () => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/components/empty_tree_placeholder.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/main/components/empty_tree_placeholder.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/index.ts b/x-pack/plugins/searchprofiler/public/application/containers/main/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/index.ts rename to x-pack/plugins/searchprofiler/public/application/containers/main/components/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.test.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/components/profile_loading_placeholder.test.tsx similarity index 86% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.test.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/main/components/profile_loading_placeholder.test.tsx index 9b3348b4cab4b2..1428840a9464fa 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/main/components/profile_loading_placeholder.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed } from '../../../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../../../test_utils'; import { ProfileLoadingPlaceholder } from '.'; describe('Profile Loading Placeholder', () => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/components/profile_loading_placeholder.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/main/components/profile_loading_placeholder.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/index.ts b/x-pack/plugins/searchprofiler/public/application/containers/main/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/index.ts rename to x-pack/plugins/searchprofiler/public/application/containers/main/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx b/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx similarity index 96% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx index 63ae5c7583625a..06bcfb9dcb9c10 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/main/main.tsx @@ -42,7 +42,7 @@ function hasAggregations(profileResponse: ShardSerialized[]) { } export const Main = () => { - const { licenseEnabled } = useAppContext(); + const { getLicenseStatus } = useAppContext(); const { activeTab, @@ -63,7 +63,7 @@ export const Main = () => { ]); const renderLicenseWarning = () => { - return !licenseEnabled ? ( + return !getLicenseStatus().valid ? ( <> @@ -84,7 +84,7 @@ export const Main = () => { ); } - if (licenseEnabled && pristine) { + if (getLicenseStatus().valid && pristine) { return ; } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/profile_query_editor.tsx b/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx similarity index 97% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/profile_query_editor.tsx rename to x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx index b879f15b729982..5348c55ad52139 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/profile_query_editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/containers/profile_query_editor.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React, { useRef, memo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -39,7 +40,7 @@ export const ProfileQueryEditor = memo(() => { const dispatch = useProfilerActionContext(); - const { licenseEnabled, notifications } = useAppContext(); + const { getLicenseStatus, notifications } = useAppContext(); const requestProfile = useRequestProfile(); const handleProfileClick = async () => { @@ -65,6 +66,7 @@ export const ProfileQueryEditor = memo(() => { }; const onEditorReady = useCallback(editorInstance => (editorRef.current = editorInstance), []); + const licenseEnabled = getLicenseStatus().valid; return ( string; + getLicenseStatus: () => LicenseStatus; } const AppContext = createContext(null as any); export const AppContextProvider = ({ children, - value, + args: { http, notifications, initialLicenseStatus }, }: { children: React.ReactNode; - value: ContextValue; + args: ContextArgs; }) => { - return {children}; + const getLicenseStatus = useCallback(() => initialLicenseStatus, [initialLicenseStatus]); + + return ( + + {children} + + ); }; export const useAppContext = () => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/contexts/profiler_context.tsx b/x-pack/plugins/searchprofiler/public/application/contexts/profiler_context.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/contexts/profiler_context.tsx rename to x-pack/plugins/searchprofiler/public/application/contexts/profiler_context.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx similarity index 91% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.test.tsx rename to x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx index d6702ef080ab73..a70d70f117edb6 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx @@ -10,7 +10,7 @@ jest.mock('./worker', () => { return { workerModule: { id: 'ace/mode/json_worker', src: '' } }; }); -import { registerTestBed } from '../../../../../../../test_utils'; +import { registerTestBed } from '../../../../../test_utils'; import { Editor, Props } from '.'; describe('Editor Component', () => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx similarity index 84% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.tsx rename to x-pack/plugins/searchprofiler/public/application/editor/editor.tsx index 014336f379059a..5f8ab776a7672c 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx @@ -10,10 +10,7 @@ import { Editor as AceEditor } from 'brace'; import { initializeEditor } from './init_editor'; import { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode'; -interface EditorShim { - getValue(): string; - focus(): void; -} +type EditorShim = ReturnType; export type EditorInstance = EditorShim; @@ -23,7 +20,7 @@ export interface Props { onEditorReady: (editor: EditorShim) => void; } -const createEditorShim = (aceEditor: AceEditor): EditorShim => { +const createEditorShim = (aceEditor: AceEditor) => { return { getValue() { return aceEditor.getValue(); @@ -40,15 +37,13 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro const [textArea, setTextArea] = useState(null); - if (licenseEnabled) { - useUIAceKeyboardMode(textArea); - } + useUIAceKeyboardMode(textArea); useEffect(() => { const divEl = containerRef.current; editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled }); editorInstanceRef.current.setValue(initialValue, 1); - setTextArea(containerRef.current!.querySelector('textarea')); + setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null); onEditorReady(createEditorShim(editorInstanceRef.current)); }, [initialValue, onEditorReady, licenseEnabled]); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/index.ts b/x-pack/plugins/searchprofiler/public/application/editor/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/index.ts rename to x-pack/plugins/searchprofiler/public/application/editor/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/init_editor.ts b/x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/init_editor.ts rename to x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/use_ui_ace_keyboard_mode.tsx b/x-pack/plugins/searchprofiler/public/application/editor/use_ui_ace_keyboard_mode.tsx similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/use_ui_ace_keyboard_mode.tsx rename to x-pack/plugins/searchprofiler/public/application/editor/use_ui_ace_keyboard_mode.tsx diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/index.ts b/x-pack/plugins/searchprofiler/public/application/editor/worker/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/index.ts rename to x-pack/plugins/searchprofiler/public/application/editor/worker/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/worker.d.ts b/x-pack/plugins/searchprofiler/public/application/editor/worker/worker.d.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/worker.d.ts rename to x-pack/plugins/searchprofiler/public/application/editor/worker/worker.d.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/worker.js b/x-pack/plugins/searchprofiler/public/application/editor/worker/worker.js similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/worker/worker.js rename to x-pack/plugins/searchprofiler/public/application/editor/worker/worker.js diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/x_json_highlight_rules.ts b/x-pack/plugins/searchprofiler/public/application/editor/x_json_highlight_rules.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/x_json_highlight_rules.ts rename to x-pack/plugins/searchprofiler/public/application/editor/x_json_highlight_rules.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/x_json_mode.ts b/x-pack/plugins/searchprofiler/public/application/editor/x_json_mode.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/editor/x_json_mode.ts rename to x-pack/plugins/searchprofiler/public/application/editor/x_json_mode.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/hooks/index.ts b/x-pack/plugins/searchprofiler/public/application/hooks/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/hooks/index.ts rename to x-pack/plugins/searchprofiler/public/application/hooks/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/hooks/use_request_profile.ts b/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts similarity index 73% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/hooks/use_request_profile.ts rename to x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts index 34b49be7dc39c6..3d8bee1d62b274 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/hooks/use_request_profile.ts +++ b/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts @@ -19,8 +19,22 @@ interface ReturnValue { error?: string; } +const extractProfilerErrorMessage = (e: any): string | undefined => { + if (e.body?.attributes?.error?.reason) { + const { reason, line, col } = e.body.attributes.error; + return `${reason} at line: ${line - 1} col: ${col}`; + } + + if (e.body?.message) { + return e.body.message; + } + + return; +}; + export const useRequestProfile = () => { - const { http, notifications, formatAngularHttpError, licenseEnabled } = useAppContext(); + const { http, notifications, getLicenseStatus } = useAppContext(); + const licenseEnabled = getLicenseStatus().valid; return async ({ query, index }: Args): Promise => { if (!licenseEnabled) { return { data: null }; @@ -39,7 +53,7 @@ export const useRequestProfile = () => { return { data: parsed.profile.shards }; } - const payload: Record = { query }; + const payload: Record = { query: parsed }; if (index == null || index === '') { payload.index = '_all'; @@ -59,11 +73,13 @@ export const useRequestProfile = () => { return { data: resp.resp.profile.shards }; } catch (e) { - try { - // Is this a known error type? - const errorString = formatAngularHttpError(e); - notifications.addError(e, { title: errorString }); - } catch (_) { + const profilerErrorMessage = extractProfilerErrorMessage(e); + if (profilerErrorMessage) { + notifications.addError(e, { + title: e.message, + toastMessage: profilerErrorMessage, + }); + } else { // Otherwise just report the original error notifications.addError(e, { title: i18n.translate('xpack.searchProfiler.errorSomethingWentWrongTitle', { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.tsx b/x-pack/plugins/searchprofiler/public/application/index.tsx similarity index 74% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.tsx rename to x-pack/plugins/searchprofiler/public/application/index.tsx index d29f193ce9d90d..fd8ab61eb9b089 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.tsx +++ b/x-pack/plugins/searchprofiler/public/application/index.tsx @@ -10,16 +10,10 @@ import { ProfileContextProvider } from './contexts/profiler_context'; import { AppDependencies } from './boot'; -export function App({ - I18nContext, - licenseEnabled, - notifications, - http, - formatAngularHttpError, -}: AppDependencies) { +export function App({ I18nContext, initialLicenseStatus, notifications, http }: AppDependencies) { return ( - +
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/index.ts b/x-pack/plugins/searchprofiler/public/application/store/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/index.ts rename to x-pack/plugins/searchprofiler/public/application/store/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.test.ts b/x-pack/plugins/searchprofiler/public/application/store/reducer.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.test.ts rename to x-pack/plugins/searchprofiler/public/application/store/reducer.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts b/x-pack/plugins/searchprofiler/public/application/store/reducer.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts rename to x-pack/plugins/searchprofiler/public/application/store/reducer.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts b/x-pack/plugins/searchprofiler/public/application/store/store.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts rename to x-pack/plugins/searchprofiler/public/application/store/store.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts b/x-pack/plugins/searchprofiler/public/application/types.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts rename to x-pack/plugins/searchprofiler/public/application/types.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/check_for_json_errors.test.ts b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/check_for_json_errors.test.ts rename to x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/check_for_json_errors.ts b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/check_for_json_errors.ts rename to x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/index.ts b/x-pack/plugins/searchprofiler/public/application/utils/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/index.ts rename to x-pack/plugins/searchprofiler/public/application/utils/index.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ms_to_pretty.ts b/x-pack/plugins/searchprofiler/public/application/utils/ms_to_pretty.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ms_to_pretty.ts rename to x-pack/plugins/searchprofiler/public/application/utils/ms_to_pretty.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ns_to_pretty.test.ts b/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ns_to_pretty.test.ts rename to x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.test.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ns_to_pretty.ts b/x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/utils/ns_to_pretty.ts rename to x-pack/plugins/searchprofiler/public/application/utils/ns_to_pretty.ts diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/index.ts b/x-pack/plugins/searchprofiler/public/index.ts similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/index.ts rename to x-pack/plugins/searchprofiler/public/index.ts diff --git a/x-pack/plugins/searchprofiler/public/plugin.ts b/x-pack/plugins/searchprofiler/public/plugin.ts new file mode 100644 index 00000000000000..8f44e65e4546ae --- /dev/null +++ b/x-pack/plugins/searchprofiler/public/plugin.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { Plugin, CoreStart, CoreSetup, PluginInitializerContext } from 'kibana/public'; +import { first } from 'rxjs/operators'; + +import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; +import { LICENSE_CHECK_STATE } from '../../licensing/public'; + +import { PLUGIN } from '../common'; +import { AppPublicPluginDependencies } from './types'; + +export class SearchProfilerUIPlugin implements Plugin { + constructor(ctx: PluginInitializerContext) {} + + async setup( + { http, getStartServices }: CoreSetup, + { dev_tools, home, licensing }: AppPublicPluginDependencies + ) { + home.featureCatalogue.register({ + id: PLUGIN.id, + title: i18n.translate('xpack.searchProfiler.registryProviderTitle', { + defaultMessage: 'Search Profiler', + }), + description: i18n.translate('xpack.searchProfiler.registryProviderDescription', { + defaultMessage: 'Quickly check the performance of any Elasticsearch query.', + }), + icon: 'searchProfilerApp', + path: '/app/kibana#/dev_tools/searchprofiler', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, + }); + + dev_tools.register({ + id: 'searchprofiler', + title: i18n.translate('xpack.searchProfiler.pageDisplayName', { + defaultMessage: 'Search Profiler', + }), + order: 5, + enableRouting: false, + mount: async (ctx, params) => { + const [coreStart] = await getStartServices(); + const { notifications, i18n: i18nDep } = coreStart; + const { boot } = await import('./application/boot'); + + const license = await licensing.license$.pipe(first()).toPromise(); + const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType); + const initialLicenseStatus = + state === LICENSE_CHECK_STATE.Valid ? { valid: true } : { valid: false, message }; + + return boot({ + http, + initialLicenseStatus, + el: params.element, + I18nContext: i18nDep.Context, + notifications: notifications.toasts, + }); + }, + }); + } + + async start(core: CoreStart, plugins: any) {} + + async stop() {} +} diff --git a/x-pack/plugins/searchprofiler/public/types.ts b/x-pack/plugins/searchprofiler/public/types.ts new file mode 100644 index 00000000000000..697761669dd2f4 --- /dev/null +++ b/x-pack/plugins/searchprofiler/public/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { DevToolsSetup } from '../../../../src/plugins/dev_tools/public'; +import { LicensingPluginSetup } from '../../licensing/public'; + +export interface AppPublicPluginDependencies { + licensing: LicensingPluginSetup; + home: HomePublicPluginSetup; + dev_tools: DevToolsSetup; +} diff --git a/x-pack/plugins/searchprofiler/server/index.ts b/x-pack/plugins/searchprofiler/server/index.ts new file mode 100644 index 00000000000000..459cd6c344d923 --- /dev/null +++ b/x-pack/plugins/searchprofiler/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext } from 'kibana/server'; +import { SearchProfilerServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => { + return new SearchProfilerServerPlugin(ctx); +}; diff --git a/x-pack/plugins/searchprofiler/server/plugin.ts b/x-pack/plugins/searchprofiler/server/plugin.ts new file mode 100644 index 00000000000000..e446b864287157 --- /dev/null +++ b/x-pack/plugins/searchprofiler/server/plugin.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; + +import { LICENSE_CHECK_STATE } from '../../licensing/common/types'; + +import { LicenseStatus, PLUGIN } from '../common'; +import { AppServerPluginDependencies } from './types'; +import * as profileRoute from './routes/profile'; + +export class SearchProfilerServerPlugin implements Plugin { + licenseStatus: LicenseStatus; + log: Logger; + + constructor({ logger }: PluginInitializerContext) { + this.log = logger.get(); + this.licenseStatus = { valid: false }; + } + + async setup({ http }: CoreSetup, { licensing, elasticsearch }: AppServerPluginDependencies) { + const router = http.createRouter(); + profileRoute.register({ + elasticsearch, + router, + getLicenseStatus: () => this.licenseStatus, + log: this.log, + }); + + licensing.license$.subscribe(license => { + const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType); + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + if (hasRequiredLicense) { + this.licenseStatus = { valid: true }; + } else { + this.licenseStatus = { + valid: false, + message: + message || + // Ensure that there is a message when license check fails + i18n.translate('xpack.searchProfiler.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }; + if (message) { + this.log.info(message); + } + } + }); + } + + start() {} + + stop() {} +} diff --git a/x-pack/plugins/searchprofiler/server/routes/profile.ts b/x-pack/plugins/searchprofiler/server/routes/profile.ts new file mode 100644 index 00000000000000..c47ab81b2ab7e6 --- /dev/null +++ b/x-pack/plugins/searchprofiler/server/routes/profile.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { RouteDependencies } from '../types'; + +export const register = ({ router, getLicenseStatus, log }: RouteDependencies) => { + router.post( + { + path: '/api/searchprofiler/profile', + validate: { + body: schema.object({ + query: schema.object({}, { allowUnknowns: true }), + index: schema.string(), + }), + }, + }, + async (ctx, request, response) => { + const currentLicenseStatus = getLicenseStatus(); + if (!currentLicenseStatus.valid) { + return response.forbidden({ + body: { + message: currentLicenseStatus.message!, + }, + }); + } + + const { + core: { elasticsearch }, + } = ctx; + + const { + body: { query, index }, + } = request; + + const parsed = { + // Activate profiler mode for this query. + profile: true, + ...query, + }; + + const body = { + index, + body: JSON.stringify(parsed, null, 2), + }; + try { + const resp = await elasticsearch.dataClient.callAsCurrentUser('search', body); + return response.ok({ + body: { + ok: true, + resp, + }, + }); + } catch (err) { + log.error(err); + return response.customError({ + statusCode: err.status || 500, + body: err.body + ? { + message: err.message, + attributes: err.body, + } + : err, + }); + } + } + ); +}; diff --git a/x-pack/plugins/searchprofiler/server/types.ts b/x-pack/plugins/searchprofiler/server/types.ts new file mode 100644 index 00000000000000..7aa0032afba138 --- /dev/null +++ b/x-pack/plugins/searchprofiler/server/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter, Logger } from 'kibana/server'; +import { ElasticsearchPlugin } from '../../../../src/legacy/core_plugins/elasticsearch'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { LicenseStatus } from '../common'; + +export interface AppServerPluginDependencies { + licensing: LicensingPluginSetup; + elasticsearch: ElasticsearchPlugin; +} + +export interface RouteDependencies { + getLicenseStatus: () => LicenseStatus; + elasticsearch: ElasticsearchPlugin; + router: IRouter; + log: Logger; +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f61dfa8d886c28..ae2db56f2acd51 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10495,7 +10495,6 @@ "xpack.searchProfiler.highlightDetails.totalTimeTooltip": "子を除き、このクエリコンポーネントだけに使用された合計時間です", "xpack.searchProfiler.highlightDetails.typeTitle": "タイプ", "xpack.searchProfiler.licenseErrorMessageDescription": "さらに可視化するには有効なライセンス ({licenseTypeList} または {platinumLicenseType}), が必要ですが、クラスターに見つかりませんでした。", - "xpack.searchProfiler.licenseHasExpiredMessage": "検索プロフィールを利用できません。ライセンスが期限切れです。", "xpack.searchProfiler.pageDisplayName": "検索プロファイラー", "xpack.searchProfiler.platinumLicenseTitle": "プラチナ", "xpack.searchProfiler.profileTree.cumulativeTimeTitle": "累積時間:", @@ -10510,8 +10509,6 @@ "xpack.searchProfiler.registryProviderDescription": "Elasticsearch クエリのパフォーマンスを素早く確認します.", "xpack.searchProfiler.registryProviderTitle": "検索プロファイラー", "xpack.searchProfiler.trialLicenseTitle": "トライアル", - "xpack.searchProfiler.unavailableLicenseInformationMessage": "検索プロファイラーを利用できません。現在ライセンス情報が利用できません。", - "xpack.searchProfiler.upgradeLicenseMessage": "現在の {licenseInfo} ライセンスでは検索プロファイラーを利用できません。ライセンスをアップグレードしてください。", "xpack.security.account.breadcrumb": "アカウント管理", "xpack.security.account.changePasswordDescription": "アカウントのパスワードを変更します。", "xpack.security.account.changePasswordForm.cancelButtonLabel": "リセット", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2c2e5325969838..e655aea1aa56f6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10494,7 +10494,6 @@ "xpack.searchProfiler.highlightDetails.totalTimeTooltip": "此查询组件花费的总时间(包括子项)", "xpack.searchProfiler.highlightDetails.typeTitle": "类型", "xpack.searchProfiler.licenseErrorMessageDescription": "分析器可视化需要有效的许可({licenseTypeList}或{platinumLicenseType},但在您的集群中未找到任何许可。", - "xpack.searchProfiler.licenseHasExpiredMessage": "Search Profiler 不可用 - 许可已过期。", "xpack.searchProfiler.pageDisplayName": "Search Profiler", "xpack.searchProfiler.platinumLicenseTitle": "白金级", "xpack.searchProfiler.profileTree.cumulativeTimeTitle": "累计时间:", @@ -10509,8 +10508,6 @@ "xpack.searchProfiler.registryProviderDescription": "快速检查任何 Elasticsearch 查询的性能。", "xpack.searchProfiler.registryProviderTitle": "Search Profiler", "xpack.searchProfiler.trialLicenseTitle": "试用", - "xpack.searchProfiler.unavailableLicenseInformationMessage": "Search Profiler 不可用 - 许可信息当前不可用。", - "xpack.searchProfiler.upgradeLicenseMessage": "Search Profiler 不可用于当前的{licenseInfo}许可。请升级您的许可。", "xpack.security.account.breadcrumb": "帐户管理", "xpack.security.account.changePasswordDescription": "为您的帐户更改密码。", "xpack.security.account.changePasswordForm.cancelButtonLabel": "重置", diff --git a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts index f8eea76026cc2d..56d7a66930ebfb 100644 --- a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts +++ b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts @@ -51,7 +51,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await aceEditor.setValue(editorTestSubjectSelector, input); await retry.waitFor( - `parser errors to match expection: HAS ${expectation ? 'ERRORS' : 'NO ERRORS'}`, + `parser errors to match expectation: HAS ${expectation ? 'ERRORS' : 'NO ERRORS'}`, async () => { const actual = await aceEditor.hasParseErrors(editorTestSubjectSelector); return expectation === actual; From b8b1c0283df17c62d1ab6c18349a5e60567ad5dd Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Wed, 22 Jan 2020 13:07:07 +0000 Subject: [PATCH 56/59] [ML] Fixes permissions checks for data visualizer create job links (#55431) * [ML] Fixes permissions checks for data visualizer create job links * [ML] Edits to permissions check following review * [ML] Revert unintentional additions --- .../datavisualizer/index_based/page.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 68856eb42973bd..721b8a458efc19 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -33,6 +33,8 @@ import { NavigationMenu } from '../../components/navigation_menu'; import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { isFullLicense } from '../../license/check_license'; +import { checkPermission } from '../../privilege/check_privilege'; +import { mlNodesAvailable } from '../../ml_nodes_check/check_ml_nodes'; import { FullTimeRangeSelector } from '../../components/full_time_range_selector'; import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service'; import { useKibanaContext, SavedSearchQuery } from '../../contexts/kibana'; @@ -130,9 +132,11 @@ export const Page: FC = () => { const defaults = getDefaultPageState(); - const [showActionsPanel] = useState( - isFullLicense() && currentIndexPattern.timeFieldName !== undefined - ); + const showActionsPanel = + isFullLicense() && + checkPermission('canCreateJob') && + mlNodesAvailable() && + currentIndexPattern.timeFieldName !== undefined; const [searchString, setSearchString] = useState(defaults.searchString); const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); @@ -613,7 +617,9 @@ export const Page: FC = () => { )} - + {showActionsPanel === true && ( + + )} From d47d20e75824334b8c52928e9f249cd20d18d5d7 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 22 Jan 2020 13:09:58 +0000 Subject: [PATCH 57/59] [ML] Fixing ML's watcher integration (#55439) --- .../create_watch_flyout/create_watch_view.js | 20 +-- .../create_watch_flyout/select_severity.tsx | 133 ++++++++++++++++++ .../application/services/http_service.ts | 5 +- 3 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js index 0a823726a2d680..7a855301885a92 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js @@ -21,7 +21,7 @@ import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { parseInterval } from '../../../../../../common/util/parse_interval'; import { ml } from '../../../../services/ml_api_service'; -import { SelectSeverity } from '../../../../components/controls/select_severity/select_severity'; +import { SelectSeverity } from './select_severity'; import { mlCreateWatchService } from './create_watch_service'; const STATUS = mlCreateWatchService.STATUS; @@ -111,19 +111,6 @@ export const CreateWatch = injectI18n( render() { const { intl } = this.props; - const mlSelectSeverityService = { - state: { - set: (name, threshold) => { - this.onThresholdChange(threshold); - return { - changed: () => {}, - }; - }, - get: () => { - return this.config.threshold; - }, - }, - }; const { status } = this.state; if (status === null || status === STATUS.SAVING || status === STATUS.SAVE_FAILED) { @@ -164,10 +151,7 @@ export const CreateWatch = injectI18n(
- +
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx new file mode 100644 index 00000000000000..8b0e7da2a5637d --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * React component for rendering a select element with threshold levels. + * This is basically a copy of SelectSeverity in public/application/components/controls/select_severity + * but which stores its state internally rather than in the appState + */ +import React, { Fragment, FC, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; + +import { getSeverityColor } from '../../../../../../common/util/anomaly_utils'; + +const warningLabel = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { + defaultMessage: 'warning', +}); +const minorLabel = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { + defaultMessage: 'minor', +}); +const majorLabel = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { + defaultMessage: 'major', +}); +const criticalLabel = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { + defaultMessage: 'critical', +}); + +const optionsMap = { + [warningLabel]: 0, + [minorLabel]: 25, + [majorLabel]: 50, + [criticalLabel]: 75, +}; + +interface TableSeverity { + val: number; + display: string; + color: string; +} + +export const SEVERITY_OPTIONS: TableSeverity[] = [ + { + val: 0, + display: warningLabel, + color: getSeverityColor(0), + }, + { + val: 25, + display: minorLabel, + color: getSeverityColor(25), + }, + { + val: 50, + display: majorLabel, + color: getSeverityColor(50), + }, + { + val: 75, + display: criticalLabel, + color: getSeverityColor(75), + }, +]; + +function optionValueToThreshold(value: number) { + // Get corresponding threshold object with required display and val properties from the specified value. + let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); + + // Default to warning if supplied value doesn't map to one of the options. + if (threshold === undefined) { + threshold = SEVERITY_OPTIONS[0]; + } + + return threshold; +} + +const TABLE_SEVERITY_DEFAULT = SEVERITY_OPTIONS[0]; + +const getSeverityOptions = () => + SEVERITY_OPTIONS.map(({ color, display, val }) => ({ + value: display, + inputDisplay: ( + + + {display} + + + ), + dropdownDisplay: ( + + + {display} + + + +

+ +

+
+
+ ), + })); + +interface Props { + onChange: (sev: TableSeverity) => void; +} + +export const SelectSeverity: FC = ({ onChange }) => { + const [severity, setSeverity] = useState(TABLE_SEVERITY_DEFAULT); + + const onSeverityChange = (valueDisplay: string) => { + const option = optionValueToThreshold(optionsMap[valueDisplay]); + setSeverity(option); + onChange(option); + }; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/services/http_service.ts b/x-pack/legacy/plugins/ml/public/application/services/http_service.ts index 1d68ec5b886eb0..41200759b7c8a1 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/http_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/http_service.ts @@ -53,7 +53,10 @@ export function http(options: any) { fetch(url, payload) .then(resp => { - resp.json().then(resp.ok === true ? resolve : reject); + resp + .json() + .then(resp.ok === true ? resolve : reject) + .catch(resp.ok === true ? resolve : reject); }) .catch(resp => { reject(resp); From 07d1dac0da03cab245bc9f68fdd66afdd4b5cf97 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 22 Jan 2020 14:28:28 +0100 Subject: [PATCH 58/59] [ML] Fix entity controls update. (#55524) Fixes an issue where the options in the internal state of the EntityControl component wouldn't update after a prop change. This had the effect that after a job change via the job selector, the entity control dropdown would stay empty. --- .../components/entity_control/entity_control.tsx | 12 +++++++++--- .../timeseriesexplorer/timeseriesexplorer.js | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx index df5412e609a9c9..6727102f55a52d 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEqual } from 'lodash'; import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -47,8 +48,8 @@ export class EntityControl extends Component { return { label: value }; }); @@ -163,7 +165,6 @@ export class TimeSeriesExplorer extends React.Component { selectedDetectorIndex: PropTypes.number, selectedEntities: PropTypes.object, selectedForecastId: PropTypes.string, - setGlobalState: PropTypes.func.isRequired, tableInterval: PropTypes.string, tableSeverity: PropTypes.number, zoom: PropTypes.object, From 7b145bffa0128b03dae5bfeb55aab1280d80826f Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 22 Jan 2020 08:35:18 -0500 Subject: [PATCH 59/59] [Watcher] Add support for additional watch action statuses (#55092) --- .../watcher/common/constants/action_states.ts | 5 ++ .../application/components/watch_status.tsx | 2 + .../action_status/__tests__/action_status.js | 53 ++++++++++++------- .../models/action_status/action_status.js | 21 +++----- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/x-pack/legacy/plugins/watcher/common/constants/action_states.ts b/x-pack/legacy/plugins/watcher/common/constants/action_states.ts index e9501fc2a60c4e..ac4da650463f84 100644 --- a/x-pack/legacy/plugins/watcher/common/constants/action_states.ts +++ b/x-pack/legacy/plugins/watcher/common/constants/action_states.ts @@ -36,4 +36,9 @@ export const ACTION_STATES: { [key: string]: string } = { CONFIG_ERROR: i18n.translate('xpack.watcher.constants.actionStates.configErrorStateText', { defaultMessage: 'Config error', }), + + // Action status is unknown; we should never end up in this state + UNKNOWN: i18n.translate('xpack.watcher.constants.actionStates.unknownStateText', { + defaultMessage: 'Unknown', + }), }; diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx index 8afd174f8561e6..a254f43723877a 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx @@ -23,6 +23,8 @@ function StatusIcon({ status }: { status: string }) { return ; case WATCH_STATES.CONFIG_ERROR: case WATCH_STATES.ERROR: + case ACTION_STATES.UNKNOWN: + return ; case ACTION_STATES.CONFIG_ERROR: case ACTION_STATES.ERROR: return ; diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js index 21e27a1b12c460..7b55ff16926033 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js @@ -106,11 +106,39 @@ describe('action_status', () => { }; }); - it(`correctly calculates ACTION_STATES.ERROR`, () => { - upstreamJson.actionStatusJson.last_execution.successful = false; - const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + describe(`correctly calculates ACTION_STATES.ERROR`, () => { + it('lastExecutionSuccessful is equal to false', () => { + upstreamJson.actionStatusJson.last_execution.successful = false; + const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); - expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + it('action is acked and lastAcknowledged is less than lastExecution', () => { + const actionStatus = ActionStatus.fromUpstreamJson({ + ...upstreamJson, + actionStatusJson: { + ack: { + state: 'acked', + timestamp: '2017-03-01T00:00:00.000Z', + }, + last_execution: { + timestamp: '2017-03-02T00:00:00.000Z', + }, + }, + }); + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); + + it('action is ackable and lastSuccessfulExecution is less than lastExecution', () => { + delete upstreamJson.actionStatusJson.last_throttle; + upstreamJson.actionStatusJson.ack.state = 'ackable'; + upstreamJson.actionStatusJson.last_successful_execution.timestamp = + '2017-03-01T00:00:00.000Z'; + upstreamJson.actionStatusJson.last_execution.timestamp = '2017-03-02T00:00:00.000Z'; + const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); }); it('correctly calculates ACTION_STATES.CONFIG_ERROR', () => { @@ -192,18 +220,7 @@ describe('action_status', () => { }); }); - it(`correctly calculates ACTION_STATES.ERROR`, () => { - delete upstreamJson.actionStatusJson.last_throttle; - upstreamJson.actionStatusJson.ack.state = 'ackable'; - upstreamJson.actionStatusJson.last_successful_execution.timestamp = - '2017-03-01T00:00:00.000Z'; - upstreamJson.actionStatusJson.last_execution.timestamp = '2017-03-02T00:00:00.000Z'; - const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); - - expect(actionStatus.state).to.be(ACTION_STATES.ERROR); - }); - - it(`throws an error if it can not determine ACTION_STATE`, () => { + it(`correctly calculates ACTION_STATES.UNKNOWN if it can not determine state`, () => { upstreamJson = { id: 'my-action', actionStatusJson: { @@ -213,9 +230,7 @@ describe('action_status', () => { }; const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); - expect(() => { - actionStatus.state; - }).to.throwError(/could not determine action status/i); + expect(actionStatus.state).to.be(ACTION_STATES.UNKNOWN); }); }); diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js index 6a484e7d4303a1..9f6b7da1e3a699 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { badImplementation, badRequest } from 'boom'; +import { badRequest } from 'boom'; import { getMoment } from '../../../../common/lib/get_moment'; import { ACTION_STATES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; @@ -46,6 +46,11 @@ export class ActionStatus { return ACTION_STATES.ACKNOWLEDGED; } + // A user could potentially land in this state if running on multiple nodes and timing is off + if (ackState === 'acked' && this.lastAcknowledged < this.lastExecution) { + return ACTION_STATES.ERROR; + } + if (ackState === 'ackable' && this.lastThrottled >= this.lastExecution) { return ACTION_STATES.THROTTLED; } @@ -58,20 +63,10 @@ export class ActionStatus { return ACTION_STATES.ERROR; } - // At this point, we cannot determine the action status so we thrown an error. + // At this point, we cannot determine the action status so mark it as "unknown". // We should never get to this point in the code. If we do, it means we are // missing an action status and the logic to determine it. - throw badImplementation( - i18n.translate( - 'xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage', - { - defaultMessage: 'Could not determine action status; action = {actionStatusJson}', - values: { - actionStatusJson: JSON.stringify(actionStatusJson), - }, - } - ) - ); + return ACTION_STATES.UNKNOWN; } get isAckable() { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ae2db56f2acd51..dbf9539f4cf0c1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12890,7 +12890,6 @@ "xpack.watcher.deleteSelectedWatchesConfirmModal.deleteButtonLabel": "{numWatchesToDelete, plural, one {ウォッチ} other {# ウォッチ}}を削除 ", "xpack.watcher.deleteSelectedWatchesConfirmModal.descriptionText": "{numWatchesToDelete, plural, one {削除されたウォッチ} other {削除されたウォッチ}}は回復できません", "xpack.watcher.models.actionStatus.actionStatusJsonPropertyMissingBadRequestMessage": "JSON引数には\"{missingProperty}\"プロパティが含まれている必要があります", - "xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage": "アクションステータスを把握できませんでした; action = {actionStatusJson}", "xpack.watcher.models.baseAction.selectMessageText": "アクションを実行します。", "xpack.watcher.models.baseAction.simulateButtonLabel": "今すぐこのアクションをシミュレート", "xpack.watcher.models.baseAction.simulateMessage": "アクション {id} のシミュレーションが完了しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e655aea1aa56f6..529763be01760b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12889,7 +12889,6 @@ "xpack.watcher.deleteSelectedWatchesConfirmModal.deleteButtonLabel": "删除 {numWatchesToDelete, plural, one {个监视} other {# 个监视}} ", "xpack.watcher.deleteSelectedWatchesConfirmModal.descriptionText": "无法恢复{numWatchesToDelete, plural, one {已删除监视} other {已删除监视}}。", "xpack.watcher.models.actionStatus.actionStatusJsonPropertyMissingBadRequestMessage": "JSON 参数必须包含“{missingProperty}”属性", - "xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage": "无法确定操作状态;操作 = {actionStatusJson}", "xpack.watcher.models.baseAction.selectMessageText": "执行操作。", "xpack.watcher.models.baseAction.simulateButtonLabel": "立即模拟此操作", "xpack.watcher.models.baseAction.simulateMessage": "已成功模拟操作 {id}",