Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Improve messaging and support for datafeed using aggregated and scripted fields #84594

Merged
merged 35 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6e9a007
[ML] Remove nested Link and Button because we don't need it anymore
qn895 Nov 17, 2020
4b9b294
[ML] Add isSupportedTimeSeriesViewJob check
qn895 Nov 18, 2020
7c5110b
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Nov 18, 2020
17c3d19
[ML] Add message
qn895 Nov 19, 2020
be013e9
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Nov 30, 2020
5f5860b
[ML] Add support for my_buckets in job creator wizard
qn895 Nov 30, 2020
30324ba
[ML] Add support for my_buckets in job creator wizard
qn895 Nov 30, 2020
12db65c
[ML] Add support for my_buckets in job creator wizard
qn895 Nov 30, 2020
2e48175
[ML] Fix so button not disabled with model plot & update message
qn895 Nov 30, 2020
8d7af66
[ML] Update i18n id
qn895 Nov 30, 2020
d4ffb10
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 1, 2020
02a579b
[ML] Update text
qn895 Dec 1, 2020
b47459d
[ML] Remove check when agg interval not same as bucket span
qn895 Dec 2, 2020
8ce8b5f
[ML] Disable SMV button
qn895 Dec 2, 2020
c259c20
Revert "[ML] Disable SMV button"
qn895 Dec 2, 2020
d83c326
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 2, 2020
499357b
[ML] Update test subjects
qn895 Dec 2, 2020
15cc5ed
[ML] Update isSourceDataChartableForDetector check
qn895 Dec 3, 2020
1394d0a
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 3, 2020
83abfec
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 6, 2020
b81230c
[ML] Add error messages on AE
qn895 Dec 7, 2020
65ac8a9
[ML] Add error messages on AE by type
qn895 Dec 7, 2020
49d11f9
[ML] Remove console.log
qn895 Dec 7, 2020
18df04e
[ML] Update type and analyticsManagementPageLink
qn895 Dec 7, 2020
3835583
[ML] Fix source data check
qn895 Dec 8, 2020
bddd17d
[ML] Fix callout missing key
qn895 Dec 8, 2020
a42acb3
[ML] Remove nested terms check and replace with more specific guard
qn895 Dec 8, 2020
f8cdf1d
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 8, 2020
ce964f5
[ML] Update messages
qn895 Dec 8, 2020
10ea7e4
[ML] Add functional test for supported and unsupported jobs with aggr…
qn895 Dec 8, 2020
49899e9
[ML] Update test
qn895 Dec 9, 2020
53d06b6
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 9, 2020
c95a44a
[ML] Move assertDisabledJobReasonWarningToastExist and update assertJ…
qn895 Dec 9, 2020
0a30cd3
[ML] Update actualJobOrGroupLabels
qn895 Dec 10, 2020
eb36a3a
Merge remote-tracking branch 'upstream/master' into ml-messaging-unsu…
qn895 Dec 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface MlSummaryJob {
nodeName?: string;
auditMessage?: Partial<AuditMessage>;
isSingleMetricViewerJob: boolean;
isNotSingleMetricViewerJobMessage?: string;
deleting?: boolean;
latestTimestampSortValue?: number;
earliestStartTimestampMs?: number;
Expand All @@ -45,6 +46,8 @@ export interface AuditMessage {
export type MlSummaryJobs = MlSummaryJob[];

export interface MlJobWithTimeRange extends CombinedJobWithStats {
id: string;
isNotSingleMetricViewerJobMessage?: string;
timeRange: {
from: number;
to: number;
Expand Down
10 changes: 7 additions & 3 deletions x-pack/plugins/ml/common/util/datafeed_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

import { Aggregation, Datafeed } from '../types/anomaly_detection_jobs';

export function getAggregations<T>(obj: any): T | undefined {
if (obj?.aggregations !== undefined) return obj.aggregations;
if (obj?.aggs !== undefined) return obj.aggs;
return undefined;
}

export const getDatafeedAggregations = (
datafeedConfig: Partial<Datafeed> | undefined
): Aggregation | undefined => {
if (datafeedConfig?.aggregations !== undefined) return datafeedConfig.aggregations;
if (datafeedConfig?.aggs !== undefined) return datafeedConfig.aggs;
return undefined;
return getAggregations<Aggregation>(datafeedConfig);
};

export const getAggregationBucketsName = (aggregations: any): string | undefined => {
Expand Down
58 changes: 44 additions & 14 deletions x-pack/plugins/ml/common/util/job_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import moment, { Duration } from 'moment';
// @ts-ignore
import numeral from '@elastic/numeral';

import { i18n } from '@kbn/i18n';
import { ALLOWED_DATA_UNITS, JOB_ID_MAX_LENGTH } from '../constants/validation';
import { parseInterval } from './parse_interval';
import { maxLengthValidator } from './validators';
Expand All @@ -20,7 +21,12 @@ import { MlServerLimits } from '../types/ml_server_info';
import { JobValidationMessage, JobValidationMessageId } from '../constants/messages';
import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../constants/aggregation_types';
import { MLCATEGORY } from '../constants/field_types';
import { getDatafeedAggregations } from './datafeed_utils';
import {
getAggregationBucketsName,
getAggregations,
getDatafeedAggregations,
} from './datafeed_utils';
import { findAggField } from './validation_utils';

export interface ValidationResults {
valid: boolean;
Expand All @@ -43,20 +49,8 @@ export function calculateDatafeedFrequencyDefaultSeconds(bucketSpanSeconds: numb
return freq;
}

// Returns a flag to indicate whether the job is suitable for viewing
// in the Time Series dashboard.
export function isTimeSeriesViewJob(job: CombinedJob): boolean {
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
// only allow jobs with at least one detector whose function corresponds to
// an ES aggregation which can be viewed in the single metric view and which
// doesn't use a scripted field which can be very difficult or impossible to
// invert to a reverse search, or when model plot has been enabled.
for (let i = 0; i < job.analysis_config.detectors.length; i++) {
if (isTimeSeriesViewDetector(job, i)) {
return true;
}
}

return false;
return getSingleMetricViewerJobErrorMessage(job) === undefined;
}

// Returns a flag to indicate whether the detector at the index in the specified job
Expand Down Expand Up @@ -99,6 +93,24 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex
scriptFields.indexOf(dtr.by_field_name!) === -1 &&
scriptFields.indexOf(dtr.over_field_name!) === -1;
}

// We cannot plot the source data for some specific aggregation configurations
const hasDatafeed =
typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0;
if (hasDatafeed) {
const aggs = getDatafeedAggregations(job.datafeed_config);
if (aggs !== undefined) {
const aggBucketsName = getAggregationBucketsName(aggs);
if (aggBucketsName !== undefined) {
// if fieldName is a aggregated field under nested terms using bucket_script
const aggregations = getAggregations<{ [key: string]: any }>(aggs[aggBucketsName]) ?? {};
const foundField = findAggField(aggregations, dtr.field_name, false);
if (foundField?.bucket_script !== undefined) {
return false;
}
}
}
}
}

return isSourceDataChartable;
Expand Down Expand Up @@ -134,6 +146,24 @@ export function isModelPlotChartableForDetector(job: Job, detectorIndex: number)
return isModelPlotChartable;
}

// Returns a reason to indicate why the job configuration is not supported
// if the result is undefined, that means the single metric job should be viewable
export function getSingleMetricViewerJobErrorMessage(job: CombinedJob): string | undefined {
// only allow jobs with at least one detector whose function corresponds to
// an ES aggregation which can be viewed in the single metric view and which
// doesn't use a scripted field which can be very difficult or impossible to
// invert to a reverse search, or when model plot has been enabled.
const isChartableTimeSeriesViewJob = job.analysis_config.detectors.some((detector, idx) =>
isTimeSeriesViewDetector(job, idx)
);

if (isChartableTimeSeriesViewJob === false) {
return i18n.translate('xpack.ml.timeSeriesJob.notViewableTimeSeriesJobMessage', {
defaultMessage: 'not a viewable time series job',
});
}
}

// Returns the names of the partition, by, and over fields for the detector with the
// specified index from the supplied ML job configuration.
export function getPartitioningFieldNames(job: CombinedJob, detectorIndex: number): string[] {
Expand Down
11 changes: 8 additions & 3 deletions x-pack/plugins/ml/common/util/validation_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,20 @@ export function isValidJson(json: string) {
}
}

export function findAggField(aggs: Record<string, any>, fieldName: string): any {
export function findAggField(
aggs: Record<string, any> | { [key: string]: any },
fieldName: string | undefined,
returnParent: boolean = false
): any {
if (fieldName === undefined) return;
let value;
Object.keys(aggs).some(function (k) {
if (k === fieldName) {
value = aggs[k];
value = returnParent === true ? aggs : aggs[k];
return true;
}
if (aggs.hasOwnProperty(k) && typeof aggs[k] === 'object') {
value = findAggField(aggs[k], fieldName);
value = findAggField(aggs[k], fieldName, returnParent);
return value !== undefined;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ export const useCreateADLinks = () => {
const userTimeSettings = useUiSettings().get(ANOMALY_DETECTION_DEFAULT_TIME_RANGE);
const createLinkWithUserDefaults = useCallback(
(location, jobList) => {
return mlJobService.createResultsUrlForJobs(
const resultsUrl = mlJobService.createResultsUrlForJobs(
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
jobList,
location,
useUserTimeSettings === true && userTimeSettings !== undefined
? userTimeSettings
: undefined
);
return `${basePath.get()}/app/ml/${resultsUrl}`;
},
[basePath]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,13 @@
import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlKibana, useMlUrlGenerator } from '../../../../../contexts/kibana';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';

export const BackToListPanel: FC = () => {
const urlGenerator = useMlUrlGenerator();
const {
services: {
application: { navigateToUrl },
},
} = useMlKibana();

const redirectToAnalyticsManagementPage = async () => {
const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
await navigateToUrl(url);
};
const analyticsManagementPageLink = useMlLink({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});

return (
<Fragment>
Expand All @@ -37,7 +29,7 @@ export const BackToListPanel: FC = () => {
defaultMessage: 'Return to the analytics management page.',
}
)}
onClick={redirectToAnalyticsManagementPage}
href={analyticsManagementPageLink}
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
data-test-subj="analyticsWizardCardManagement"
/>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,22 @@
import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlUrlGenerator } from '../../../../../contexts/kibana';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
interface Props {
jobId: string;
analysisType: DataFrameAnalysisConfigType;
}

export const ViewResultsPanel: FC<Props> = ({ jobId, analysisType }) => {
const urlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();

const redirectToAnalyticsExplorationPage = async () => {
const path = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId,
analysisType,
},
});
await navigateToPath(path);
};
const analyticsExplorationPageLink = useMlLink({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId,
analysisType,
},
});

return (
<Fragment>
Expand All @@ -45,7 +38,7 @@ export const ViewResultsPanel: FC<Props> = ({ jobId, analysisType }) => {
defaultMessage: 'View results for the analytics job.',
}
)}
onClick={redirectToAnalyticsExplorationPage}
href={analyticsExplorationPageLink}
data-test-subj="analyticsWizardViewResultsCard"
/>
</Fragment>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { MlTooltipComponent } from '../../components/chart_tooltip';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator';
import { addItemToRecentlyAccessed } from '../../util/recently_accessed';
import { ExplorerChartsErrorCallOuts } from './explorer_charts_error_callouts';

const textTooManyBuckets = i18n.translate('xpack.ml.explorer.charts.tooManyBucketsDescription', {
defaultMessage:
Expand Down Expand Up @@ -165,6 +166,7 @@ export const ExplorerChartsContainerUI = ({
severity,
tooManyBuckets,
kibana,
errorMessages,
}) => {
const {
services: {
Expand All @@ -183,27 +185,29 @@ export const ExplorerChartsContainerUI = ({
const chartsColumns = chartsPerRow === 1 ? 0 : chartsPerRow;

const wrapLabel = seriesToPlot.some((series) => isLabelLengthAboveThreshold(series));

return (
<EuiFlexGrid columns={chartsColumns}>
{seriesToPlot.length > 0 &&
seriesToPlot.map((series) => (
<EuiFlexItem
key={getChartId(series)}
className="ml-explorer-chart-container"
style={{ minWidth: chartsWidth }}
>
<ExplorerChartContainer
series={series}
severity={severity}
tooManyBuckets={tooManyBuckets}
wrapLabel={wrapLabel}
navigateToApp={navigateToApp}
mlUrlGenerator={mlUrlGenerator}
/>
</EuiFlexItem>
))}
</EuiFlexGrid>
<>
<ExplorerChartsErrorCallOuts errorMessagesByType={errorMessages} />
<EuiFlexGrid columns={chartsColumns}>
{seriesToPlot.length > 0 &&
seriesToPlot.map((series) => (
<EuiFlexItem
key={getChartId(series)}
className="ml-explorer-chart-container"
style={{ minWidth: chartsWidth }}
>
<ExplorerChartContainer
series={series}
severity={severity}
tooManyBuckets={tooManyBuckets}
wrapLabel={wrapLabel}
navigateToApp={navigateToApp}
mlUrlGenerator={mlUrlGenerator}
/>
</EuiFlexItem>
))}
</EuiFlexGrid>
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/

import type { JobId } from '../../../../common/types/anomaly_detection_jobs';

export interface ExplorerChartSeriesErrorMessages {
[key: string]: Set<JobId>;
}
export declare interface ExplorerChartsData {
chartsPerRow: number;
seriesToPlot: any[];
tooManyBuckets: boolean;
timeFieldName: string;
errorMessages: ExplorerChartSeriesErrorMessages;
}

export declare const getDefaultChartsData: () => ExplorerChartsData;
Expand Down
Loading