diff --git a/docs/visualize/tsvb.asciidoc b/docs/visualize/tsvb.asciidoc index 36709c2cc64370..9a1e81670b6541 100644 --- a/docs/visualize/tsvb.asciidoc +++ b/docs/visualize/tsvb.asciidoc @@ -122,3 +122,17 @@ Edit the source for the Markdown visualization. . To insert the mustache template variable into the editor, click the variable name. + The http://mustache.github.io/mustache.5.html[mustache syntax] uses the Handlebar.js processor, which is an extended version of the Mustache template language. + +[float] +[[tsvb-style-markdown]] +==== Style Markdown text + +Style your Markdown visualization using http://lesscss.org/features/[less syntax]. + +. Select *Markdown*. + +. Select *Panel options*. + +. Enter styling rules in *Custom CSS* section ++ +Less in TSVB does not support custom plugins or inline JavaScript. diff --git a/package.json b/package.json index 86f6e21dbf4f8d..6a844e39335f95 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,7 @@ "leaflet-responsive-popup": "0.6.4", "leaflet-vega": "^0.8.6", "leaflet.heat": "0.2.0", - "less": "^2.7.3", + "less": "npm:@elastic/less@2.7.3-kibana", "less-loader": "5.0.0", "lodash": "npm:@elastic/lodash@3.10.1-kibana4", "lodash.clonedeep": "^4.5.0", diff --git a/packages/kbn-es/src/utils/native_realm.js b/packages/kbn-es/src/utils/native_realm.js index 247ddc461910fd..086898abb6b676 100644 --- a/packages/kbn-es/src/utils/native_realm.js +++ b/packages/kbn-es/src/utils/native_realm.js @@ -76,6 +76,10 @@ exports.NativeRealm = class NativeRealm { } const reservedUsers = await this.getReservedUsers(); + if (!reservedUsers || reservedUsers.length < 1) { + throw new Error('no reserved users found, unable to set native realm passwords'); + } + await Promise.all( reservedUsers.map(async user => { await this.setPassword(user, options[`password.${user}`]); diff --git a/src/plugins/console/public/styles/_app.scss b/src/plugins/console/public/styles/_app.scss index 047db6e3ad877c..c41df24912c2a7 100644 --- a/src/plugins/console/public/styles/_app.scss +++ b/src/plugins/console/public/styles/_app.scss @@ -1,8 +1,11 @@ // TODO: Move all of the styles here (should be modularised by, e.g., CSS-in-JS or CSS modules). @import '@elastic/eui/src/components/header/variables'; +// This value is calculated to static value using SCSS because calc in calc has issues in IE11 +$headerHeightOffset: $euiHeaderHeightCompensation * 2; + #consoleRoot { - height: calc(100vh - calc(#{$euiHeaderChildSize} * 2)); + height: calc(100vh - #{$headerHeightOffset}); display: flex; flex: 1 1 auto; // Make sure the editor actions don't create scrollbars on this container diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js index ed17fceeda5401..4aa8856836fc6e 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js @@ -64,7 +64,7 @@ class MarkdownPanelConfigUi extends Component { const lessSrc = `#markdown-${model.id} { ${value} }`; - lessC.render(lessSrc, { compress: true }, (e, output) => { + lessC.render(lessSrc, { compress: true, javascriptEnabled: false }, (e, output) => { const parts = { markdown_less: value }; if (output) { parts.markdown_css = output.css; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 0cdc7c4eb124d5..d3c4654de81649 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -17,7 +17,8 @@ export const getSeverityColor = (nodeSeverity: string) => { switch (nodeSeverity) { case severity.warning: return theme.euiColorVis0; - case severity.minor || severity.major: + case severity.minor: + case severity.major: return theme.euiColorVis5; case severity.critical: return theme.euiColorVis9; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index 48e17dadc810f0..99885e5cc40fe2 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -30,33 +30,30 @@ export const DrilldownHelloBar: React.FC = ({ onHideClick = () => {}, }) => { return ( - - -
- -
-
- - - {txtHelpText} - - {docsLink && ( - <> - - {txtViewDocsLinkLabel} - - )} - - - - {txtHideHelpButtonLabel} - - - - } - /> + + + +
+ +
+
+ + + {txtHelpText} + + {docsLink && ( + <> + + {txtViewDocsLinkLabel} + + )} + + + + {txtHideHelpButtonLabel} + + +
+
); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index c1213b8ddfa1c8..c2d9113e330fcb 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -71,6 +71,7 @@ export const Expressions: React.FC = props => { fetch: alertsContext.http.fetch, toastWarning: alertsContext.toastNotifications.addWarning, }); + const [timeSize, setTimeSize] = useState(1); const [timeUnit, setTimeUnit] = useState('m'); const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ @@ -166,52 +167,57 @@ export const Expressions: React.FC = props => { [alertParams.criteria, setAlertParams] ); - useEffect(() => { + const preFillAlertCriteria = useCallback(() => { const md = alertsContext.metadata; - if (md) { - if (md.currentOptions?.metrics) { - setAlertParams( - 'criteria', - md.currentOptions.metrics.map(metric => ({ - metric: metric.field, - comparator: Comparator.GT, - threshold: [], - timeSize, - timeUnit, - aggType: metric.aggregation, - })) - ); - } else { - setAlertParams('criteria', [defaultExpression]); - } + if (md && md.currentOptions?.metrics) { + setAlertParams( + 'criteria', + md.currentOptions.metrics.map(metric => ({ + metric: metric.field, + comparator: Comparator.GT, + threshold: [], + timeSize, + timeUnit, + aggType: metric.aggregation, + })) + ); + } else { + setAlertParams('criteria', [defaultExpression]); + } + }, [alertsContext.metadata, setAlertParams, timeSize, timeUnit]); - if (md.currentOptions) { - if (md.currentOptions.filterQuery) { - setAlertParams('filterQueryText', md.currentOptions.filterQuery); - setAlertParams( - 'filterQuery', - convertKueryToElasticSearchQuery(md.currentOptions.filterQuery, derivedIndexPattern) || - '' - ); - } else if (md.currentOptions.groupBy && md.series) { - const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`; - setAlertParams('filterQueryText', filter); - setAlertParams( - 'filterQuery', - convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || '' - ); - } + const preFillAlertFilter = useCallback(() => { + const md = alertsContext.metadata; + if (md && md.currentOptions?.filterQuery) { + setAlertParams('filterQueryText', md.currentOptions.filterQuery); + setAlertParams( + 'filterQuery', + convertKueryToElasticSearchQuery(md.currentOptions.filterQuery, derivedIndexPattern) || '' + ); + } else if (md && md.currentOptions?.groupBy && md.series) { + const filter = `${md.currentOptions?.groupBy}: "${md.series.id}"`; + setAlertParams('filterQueryText', filter); + setAlertParams( + 'filterQuery', + convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || '' + ); + } + }, [alertsContext.metadata, derivedIndexPattern, setAlertParams]); - setAlertParams('groupBy', md.currentOptions.groupBy); - } - setAlertParams('sourceId', source?.id); + useEffect(() => { + if (alertParams.criteria && alertParams.criteria.length) { + setTimeSize(alertParams.criteria[0].timeSize); + setTimeUnit(alertParams.criteria[0].timeUnit); } else { - if (!alertParams.criteria) { - setAlertParams('criteria', [defaultExpression]); - } - if (!alertParams.sourceId) { - setAlertParams('sourceId', source?.id || 'default'); - } + preFillAlertCriteria(); + } + + if (!alertParams.filterQuery) { + preFillAlertFilter(); + } + + if (!alertParams.sourceId) { + setAlertParams('sourceId', source?.id || 'default'); } }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index a2c5b27c38fd68..5ca65b667ae11a 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -13,6 +13,11 @@ export const oneOfLiterals = (arrayOfLiterals: Readonly) => }); export const validateIsStringElasticsearchJSONFilter = (value: string) => { + if (value === '') { + // Allow clearing the filter. + return; + } + const errorMessage = 'filterQuery must be a valid Elasticsearch filter expressed in JSON'; try { const parsedValue = JSON.parse(value); diff --git a/x-pack/plugins/ml/common/util/job_utils.d.ts b/x-pack/plugins/ml/common/util/job_utils.d.ts index 4528fbfbb774d7..170e42aabc67dd 100644 --- a/x-pack/plugins/ml/common/util/job_utils.d.ts +++ b/x-pack/plugins/ml/common/util/job_utils.d.ts @@ -44,6 +44,8 @@ export function mlFunctionToESAggregation(functionName: string): string | null; export function isModelPlotEnabled(job: Job, detectorIndex: number, entityFields: any[]): boolean; +export function isModelPlotChartableForDetector(job: Job, detectorIndex: number): boolean; + export function getSafeAggregationName(fieldName: string, index: number): string; export function getLatestDataOrBucketTimestamp( diff --git a/x-pack/plugins/ml/common/util/job_utils.js b/x-pack/plugins/ml/common/util/job_utils.js index 8fe5733ce67bd6..1217139872fc1c 100644 --- a/x-pack/plugins/ml/common/util/job_utils.js +++ b/x-pack/plugins/ml/common/util/job_utils.js @@ -105,18 +105,20 @@ export function isModelPlotChartableForDetector(job, detectorIndex) { const dtr = dtrs[detectorIndex]; const functionName = dtr.function; - // Model plot can be charted for any of the functions which map to ES aggregations, + // Model plot can be charted for any of the functions which map to ES aggregations + // (except rare, for which no model plot results are generated), // plus varp and info_content functions. isModelPlotChartable = - mlFunctionToESAggregation(functionName) !== null || - [ - 'varp', - 'high_varp', - 'low_varp', - 'info_content', - 'high_info_content', - 'low_info_content', - ].includes(functionName) === true; + functionName !== 'rare' && + (mlFunctionToESAggregation(functionName) !== null || + [ + 'varp', + 'high_varp', + 'low_varp', + 'info_content', + 'high_info_content', + 'low_info_content', + ].includes(functionName) === true); } return isModelPlotChartable; diff --git a/x-pack/plugins/ml/common/util/job_utils.test.js b/x-pack/plugins/ml/common/util/job_utils.test.js index a5df160bdf5ca6..de269676a96ed0 100644 --- a/x-pack/plugins/ml/common/util/job_utils.test.js +++ b/x-pack/plugins/ml/common/util/job_utils.test.js @@ -307,7 +307,14 @@ describe('ML - job utils', () => { const job2 = { analysis_config: { - detectors: [{ function: 'count' }, { function: 'info_content' }], + detectors: [ + { function: 'count' }, + { function: 'info_content' }, + { + function: 'rare', + by_field_name: 'mlcategory', + }, + ], }, model_plot_config: { enabled: true, @@ -325,6 +332,10 @@ describe('ML - job utils', () => { test('returns true for info_content detector when model plot is enabled', () => { expect(isModelPlotChartableForDetector(job2, 1)).toBe(true); }); + + test('returns false for rare by mlcategory when model plot is enabled', () => { + expect(isModelPlotChartableForDetector(job2, 2)).toBe(false); + }); }); describe('getPartitioningFieldNames', () => { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index e0fb97a81f587c..fe7e436b61117d 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -19,6 +19,7 @@ import { chartLimits, getChartType } from '../../util/chart_utils'; import { getEntityFieldList } from '../../../../common/util/anomaly_utils'; import { isSourceDataChartableForDetector, + isModelPlotChartableForDetector, isModelPlotEnabled, } from '../../../../common/util/job_utils'; import { mlResultsService } from '../../services/results_service'; @@ -420,7 +421,7 @@ function processRecordsForDisplay(anomalyRecords) { // is chartable, and if model plot is enabled for the job. const job = mlJobService.getJob(record.job_id); let isChartable = isSourceDataChartableForDetector(job, record.detector_index); - if (isChartable === false) { + if (isChartable === false && isModelPlotChartableForDetector(job, record.detector_index)) { // Check if model plot is enabled for this job. // Need to check the entity fields for the record in case the model plot config has a terms list. const entityFields = getEntityFieldList(record); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index 3fcb3c351e666a..aaf9ff491ce32e 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -20,6 +20,7 @@ import { import { getEntityFieldList } from '../../../common/util/anomaly_utils'; import { isSourceDataChartableForDetector, + isModelPlotChartableForDetector, isModelPlotEnabled, } from '../../../common/util/job_utils'; import { parseInterval } from '../../../common/util/parse_interval'; @@ -636,7 +637,10 @@ export async function loadAnomaliesTableData( // TODO - when job_service is moved server_side, move this to server endpoint. const job = mlJobService.getJob(jobId); let isChartable = isSourceDataChartableForDetector(job, anomaly.detectorIndex); - if (isChartable === false) { + if ( + isChartable === false && + isModelPlotChartableForDetector(job, anomaly.detectorIndex) + ) { // Check if model plot is enabled for this job. // Need to check the entity fields for the record in case the model plot config has a terms list. // If terms is specified, model plot is only stored if both the partition and by fields appear in the list. diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index f973d41ad7754e..6e46ab0023ce42 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -9,7 +9,10 @@ import _ from 'lodash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ml } from '../services/ml_api_service'; -import { isModelPlotEnabled } from '../../../common/util/job_utils'; +import { + isModelPlotChartableForDetector, + isModelPlotEnabled, +} from '../../../common/util/job_utils'; // @ts-ignore import { buildConfigFromDetector } from '../util/chart_config_builder'; import { mlResultsService } from '../services/results_service'; @@ -24,7 +27,10 @@ function getMetricData( latestMs: number, interval: string ): Observable { - if (isModelPlotEnabled(job, detectorIndex, entityFields)) { + if ( + isModelPlotChartableForDetector(job, detectorIndex) && + isModelPlotEnabled(job, detectorIndex, entityFields) + ) { // Extract the partition, by, over fields on which to filter. const criteriaFields = []; const detector = job.analysis_config.detectors[detectorIndex]; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 45f495299bc694..8bf42fe545152e 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -36,6 +36,7 @@ import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; import { isModelPlotEnabled, + isModelPlotChartableForDetector, isSourceDataChartableForDetector, isTimeSeriesViewDetector, mlFunctionToESAggregation, @@ -506,11 +507,9 @@ export class TimeSeriesExplorer extends React.Component { contextForecastData: undefined, focusChartData: undefined, focusForecastData: undefined, - modelPlotEnabled: isModelPlotEnabled( - currentSelectedJob, - selectedDetectorIndex, - entityControls - ), + modelPlotEnabled: + isModelPlotChartableForDetector(currentSelectedJob, selectedDetectorIndex) && + isModelPlotEnabled(currentSelectedJob, selectedDetectorIndex, entityControls), hasResults: false, dataNotChartable: false, } diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 645625f92df295..8ccd359137b670 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -342,8 +342,8 @@ export class DataVisualizer { aggregatableFields: string[], samplerShardSize: number, timeFieldName: string, - earliestMs: number, - latestMs: number + earliestMs?: number, + latestMs?: number ) { const index = indexPatternTitle; const size = 0; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts deleted file mode 100644 index 2fad1252e64465..00000000000000 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts +++ /dev/null @@ -1,13 +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 { APICaller } from 'kibana/server'; -import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; - -export function validateCardinality( - callAsCurrentUser: APICaller, - job?: CombinedJob -): Promise; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts similarity index 76% rename from x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js rename to x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts index 22e0510782e115..cf3d6d004c37e2 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; - +import { APICaller } from 'kibana/server'; import { DataVisualizer } from '../data_visualizer'; import { validateJobObject } from './validate_job_object'; +import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; +import { Detector } from '../../../common/types/anomaly_detection_jobs'; -function isValidCategorizationConfig(job, fieldName) { +function isValidCategorizationConfig(job: CombinedJob, fieldName: string): boolean { return ( typeof job.analysis_config.categorization_field_name !== 'undefined' && fieldName === 'mlcategory' ); } -function isScriptField(job, fieldName) { - const scriptFields = Object.keys(_.get(job, 'datafeed_config.script_fields', {})); +function isScriptField(job: CombinedJob, fieldName: string): boolean { + const scriptFields = Object.keys(job.datafeed_config.script_fields ?? {}); return scriptFields.includes(fieldName); } @@ -30,10 +31,21 @@ const PARTITION_FIELD_CARDINALITY_THRESHOLD = 1000; const BY_FIELD_CARDINALITY_THRESHOLD = 1000; const MODEL_PLOT_THRESHOLD_HIGH = 100; -const validateFactory = (callWithRequest, job) => { +type Messages = Array<{ id: string; fieldName?: string }>; + +type Validator = (obj: { + type: string; + isInvalid: (cardinality: number) => boolean; + messageId?: string; +}) => Promise<{ + modelPlotCardinality: number; + messages: Messages; +}>; + +const validateFactory = (callWithRequest: APICaller, job: CombinedJob): Validator => { const dv = new DataVisualizer(callWithRequest); - const modelPlotConfigTerms = _.get(job, ['model_plot_config', 'terms'], ''); + const modelPlotConfigTerms = job?.model_plot_config?.terms ?? ''; const modelPlotConfigFieldCount = modelPlotConfigTerms.length > 0 ? modelPlotConfigTerms.split(',').length : 0; @@ -42,8 +54,11 @@ const validateFactory = (callWithRequest, job) => { // if model_plot_config.terms is used, it doesn't count the real cardinality of the field // but adds only the count of fields used in model_plot_config.terms let modelPlotCardinality = 0; - const messages = []; - const fieldName = `${type}_field_name`; + const messages: Messages = []; + const fieldName = `${type}_field_name` as keyof Pick< + Detector, + 'by_field_name' | 'over_field_name' | 'partition_field_name' + >; const detectors = job.analysis_config.detectors; const relevantDetectors = detectors.filter(detector => { @@ -52,7 +67,7 @@ const validateFactory = (callWithRequest, job) => { if (relevantDetectors.length > 0) { try { - const uniqueFieldNames = _.uniq(relevantDetectors.map(f => f[fieldName])); + const uniqueFieldNames = [...new Set(relevantDetectors.map(f => f[fieldName]))] as string[]; // use fieldCaps endpoint to get data about whether fields are aggregatable const fieldCaps = await callWithRequest('fieldCaps', { @@ -60,7 +75,7 @@ const validateFactory = (callWithRequest, job) => { fields: uniqueFieldNames, }); - let aggregatableFieldNames = []; + let aggregatableFieldNames: string[] = []; // parse fieldCaps to return an array of just the fields which are aggregatable if (typeof fieldCaps === 'object' && typeof fieldCaps.fields === 'object') { aggregatableFieldNames = uniqueFieldNames.filter(field => { @@ -81,12 +96,14 @@ const validateFactory = (callWithRequest, job) => { ); uniqueFieldNames.forEach(uniqueFieldName => { - const field = _.find(stats.aggregatableExistsFields, { fieldName: uniqueFieldName }); - if (typeof field === 'object') { + const field = stats.aggregatableExistsFields.find( + fieldData => fieldData.fieldName === uniqueFieldName + ); + if (field !== undefined && typeof field === 'object' && field.stats) { modelPlotCardinality += - modelPlotConfigFieldCount > 0 ? modelPlotConfigFieldCount : field.stats.cardinality; + modelPlotConfigFieldCount > 0 ? modelPlotConfigFieldCount : field.stats.cardinality!; - if (isInvalid(field.stats.cardinality)) { + if (isInvalid(field.stats.cardinality!)) { messages.push({ id: messageId || `cardinality_${type}_field`, fieldName: uniqueFieldName, @@ -115,7 +132,7 @@ const validateFactory = (callWithRequest, job) => { if (relevantDetectors.length === 1) { messages.push({ id: 'field_not_aggregatable', - fieldName: relevantDetectors[0][fieldName], + fieldName: relevantDetectors[0][fieldName]!, }); } else { messages.push({ id: 'fields_not_aggregatable' }); @@ -129,10 +146,16 @@ const validateFactory = (callWithRequest, job) => { }; }; -export async function validateCardinality(callWithRequest, job) { +export async function validateCardinality( + callWithRequest: APICaller, + job?: CombinedJob +): Promise> | never { const messages = []; - validateJobObject(job); + if (!validateJobObject(job)) { + // required for TS type casting, validateJobObject throws an error internally. + throw new Error(); + } // find out if there are any relevant detector field names // where cardinality checks could be run against. @@ -140,14 +163,13 @@ export async function validateCardinality(callWithRequest, job) { return d.by_field_name || d.over_field_name || d.partition_field_name; }); if (numDetectorsWithFieldNames.length === 0) { - return Promise.resolve([]); + return []; } // validate({ type, isInvalid }) asynchronously returns an array of validation messages const validate = validateFactory(callWithRequest, job); - const modelPlotEnabled = - (job.model_plot_config && job.model_plot_config.enabled === true) || false; + const modelPlotEnabled = job.model_plot_config?.enabled ?? false; // check over fields (population analysis) const validateOverFieldsLow = validate({ diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_job_object.ts b/x-pack/plugins/ml/server/models/job_validation/validate_job_object.ts index b0271fb5b4f45c..0d89656e051173 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_job_object.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_job_object.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; -export function validateJobObject(job: CombinedJob | null) { +export function validateJobObject(job: CombinedJob | null | undefined): job is CombinedJob | never { if (job === null || typeof job !== 'object') { throw new Error( i18n.translate('xpack.ml.models.jobValidation.validateJobObject.jobIsNotObjectErrorMessage', { @@ -93,4 +93,5 @@ export function validateJobObject(job: CombinedJob | null) { ) ); } + return true; } diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss index df68a6450c1917..e6c9574161fd8b 100644 --- a/x-pack/plugins/painless_lab/public/styles/_index.scss +++ b/x-pack/plugins/painless_lab/public/styles/_index.scss @@ -5,7 +5,7 @@ * This is a very brittle way of preventing the editor and other content from disappearing * behind the bottom bar. */ -$bottomBarHeight: calc(#{$euiSize} * 3); +$bottomBarHeight: $euiSize * 3; .painlessLabBottomBarPlaceholder { height: $bottomBarHeight; @@ -40,8 +40,11 @@ $bottomBarHeight: calc(#{$euiSize} * 3); line-height: 0; } +// This value is calculated to static value using SCSS because calc in calc has issues in IE11 +$headerHeightOffset: $euiHeaderHeightCompensation * 2; + .painlessLabMainContainer { - height: calc(100vh - calc(#{$euiHeaderChildSize} * 2) - #{$bottomBarHeight}); + height: calc(100vh - #{$headerHeightOffset} - #{$bottomBarHeight}); } .painlessLabPanelsContainer { diff --git a/x-pack/plugins/searchprofiler/public/styles/_index.scss b/x-pack/plugins/searchprofiler/public/styles/_index.scss index 5c35e9a23b8a1a..e63042cf8fe2f7 100644 --- a/x-pack/plugins/searchprofiler/public/styles/_index.scss +++ b/x-pack/plugins/searchprofiler/public/styles/_index.scss @@ -47,8 +47,11 @@ } } +// This value is calculated to static value using SCSS because calc in calc has issues in IE11 +$headerHeightOffset: $euiHeaderHeightCompensation * 2; + .appRoot { - height: calc(100vh - calc(#{$euiHeaderChildSize} * 2)); + height: calc(100vh - #{$headerHeightOffset}); overflow: hidden; flex-shrink: 1; } diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/actions_description.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/actions_description.tsx new file mode 100644 index 00000000000000..f4df1fa4acc137 --- /dev/null +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/actions_description.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { startCase } from 'lodash/fp'; +import { AlertAction } from '../../../../../../../alerting/common'; + +const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { + if (!actions.length) return null; + + return ( +
    + {actions.map((action, index) => ( +
  • {getActionTypeName(action.actionTypeId)}
  • + ))} +
+ ); +}; + +export const buildActionsDescription = (actions: AlertAction[], title: string) => ({ + title: actions.length ? title : '', + description: , +}); + +const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => { + if (!actionTypeId) return ''; + const actionType = actionTypeId.split('.')[1]; + + if (!actionType) return ''; + + return startCase(actionType); +}; diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx index 108f2138114122..b814eb8f7bb97b 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx @@ -34,6 +34,8 @@ import { } from './helpers'; import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; import { buildMlJobDescription } from './ml_job_description'; +import { buildActionsDescription } from './actions_description'; +import { buildThrottleDescription } from './throttle_description'; const DescriptionListContainer = styled(EuiDescriptionList)` &.euiDescriptionList--column .euiDescriptionList__title { @@ -73,6 +75,15 @@ export const StepRuleDescriptionComponent: React.FC = ), ]; } + + if (key === 'throttle') { + return [...acc, buildThrottleDescription(get(key, data), get([key, 'label'], schema))]; + } + + if (key === 'actions') { + return [...acc, buildActionsDescription(get(key, data), get([key, 'label'], schema))]; + } + return [...acc, ...buildListItems(data, pick(key, schema), filterManager, indexPatterns)]; }, []); diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/throttle_description.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/throttle_description.tsx new file mode 100644 index 00000000000000..b3cdbabab36e21 --- /dev/null +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/description_step/throttle_description.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { find } from 'lodash/fp'; +import { THROTTLE_OPTIONS, DEFAULT_THROTTLE_OPTION } from '../throttle_select_field'; + +export const buildThrottleDescription = (value = DEFAULT_THROTTLE_OPTION.value, title: string) => { + const throttleOption = find(['value', value], THROTTLE_OPTIONS); + + return { + title, + description: throttleOption ? throttleOption.text : value, + }; +}; diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx index aec315938b6aef..7ffb49840eeb57 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx @@ -13,7 +13,11 @@ import { RuleStep, RuleStepProps, ActionsStepRule } from '../../types'; import { StepRuleDescription } from '../description_step'; import { Form, UseField, useForm } from '../../../../../shared_imports'; import { StepContentWrapper } from '../step_content_wrapper'; -import { ThrottleSelectField, THROTTLE_OPTIONS } from '../throttle_select_field'; +import { + ThrottleSelectField, + THROTTLE_OPTIONS, + DEFAULT_THROTTLE_OPTION, +} from '../throttle_select_field'; import { RuleActionsField } from '../rule_actions_field'; import { useKibana } from '../../../../../lib/kibana'; import { schema } from './schema'; @@ -29,7 +33,7 @@ const stepActionsDefaultValue = { isNew: true, actions: [], kibanaSiemAppUrl: '', - throttle: THROTTLE_OPTIONS[0].value, + throttle: DEFAULT_THROTTLE_OPTION.value, }; const GhostFormField = () => <>; @@ -112,7 +116,7 @@ const StepRuleActionsComponent: FC = ({ return isReadOnlyView && myStepData != null ? ( - + ) : ( <> diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index 1b27d0e0fcc0ed..78715eb5b222ca 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -11,9 +11,6 @@ import { i18n } from '@kbn/i18n'; import { FormSchema } from '../../../../../shared_imports'; export const schema: FormSchema = { - actions: {}, - enabled: {}, - kibanaSiemAppUrl: {}, throttle: { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel', @@ -29,4 +26,14 @@ export const schema: FormSchema = { } ), }, + actions: { + label: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepRuleActions.fieldActionsLabel', + { + defaultMessage: 'Actions', + } + ), + }, + enabled: {}, + kibanaSiemAppUrl: {}, }; diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx index 0cf15c41a0f913..9628ab75254041 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx @@ -20,6 +20,8 @@ export const THROTTLE_OPTIONS = [ { value: '7d', text: 'Weekly' }, ]; +export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS[0]; + type ThrottleSelectField = typeof SelectField; export const ThrottleSelectField: ThrottleSelectField = props => { diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts index 0ad9021cd7f39c..fbb54b566ece0d 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts @@ -6,6 +6,7 @@ import { SavedObjectsServiceSetup } from 'kibana/server'; import mappings from './mappings.json'; +import { migrations } from './migrations'; import { TaskManagerConfig } from '../config.js'; export function setupSavedObjects( @@ -18,6 +19,7 @@ export function setupSavedObjects( hidden: true, convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`, mappings: mappings.task, + migrations, indexPattern: config.index, }); } diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts index 1c2cf73d0fe134..98ae86b9e557f7 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts @@ -3,22 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject } from '../../../../../src/core/server'; +import { SavedObjectMigrationMap } from '../../../../../src/core/server'; -export const migrations = { - task: { - '7.4.0': (doc: SavedObject>) => ({ - ...doc, - updated_at: new Date().toISOString(), - }), - '7.6.0': moveIntervalIntoSchedule, - }, +export const migrations: SavedObjectMigrationMap = { + '7.4.0': doc => ({ + ...doc, + updated_at: new Date().toISOString(), + }), + '7.6.0': moveIntervalIntoSchedule, }; -function moveIntervalIntoSchedule({ - attributes: { interval, ...attributes }, - ...doc -}: SavedObject>) { +// Type is wrong here and will be fixed by platform in https://github.com/elastic/kibana/issues/64748 +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function moveIntervalIntoSchedule({ attributes: { interval, ...attributes }, ...doc }: any): any { return { ...doc, attributes: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 6935dda358d9c7..26c1020a8bbb29 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -152,9 +152,10 @@ export const ActionForm = ({ const preconfiguredConnectors = connectors.filter(connector => connector.isPreconfigured); const hasActionsDisabled = actions.some( action => - !actionTypesIndex![action.actionTypeId].enabled && + actionTypesIndex && + !actionTypesIndex[action.actionTypeId].enabled && !checkActionFormActionTypeEnabled( - actionTypesIndex![action.actionTypeId], + actionTypesIndex[action.actionTypeId], preconfiguredConnectors ).isEnabled ); @@ -208,7 +209,11 @@ export const ActionForm = ({ }, index: number ) => { - const actionType = actionTypesIndex![actionItem.actionTypeId]; + if (!actionTypesIndex) { + return null; + } + + const actionType = actionTypesIndex[actionItem.actionTypeId]; const optionsList = connectors .filter( @@ -226,7 +231,7 @@ export const ActionForm = ({ if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; const checkEnabledResult = checkActionFormActionTypeEnabled( - actionTypesIndex![actionConnector.actionTypeId], + actionTypesIndex[actionConnector.actionTypeId], connectors.filter(connector => connector.isPreconfigured) ); @@ -248,7 +253,8 @@ export const ActionForm = ({ /> } labelAppend={ - actionTypesIndex![actionConnector.actionTypeId].enabledInConfig ? ( + actionTypesIndex && + actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? (