Skip to content

Commit

Permalink
[ML] Anomaly detection job custom_settings improvements (#102099)
Browse files Browse the repository at this point in the history
* [ML] Anomaly detection job custom_settings improvements

* filter improvements

* translations

* fixing types

* fixing tests

* one more test fix

* fixing bug with expanded row

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jgowdyelastic and kibanamachine committed Jun 18, 2021
1 parent 7267f50 commit 0ef1c3d
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface MlSummaryJob {
earliestStartTimestampMs?: number;
awaitingNodeAssignment: boolean;
alertingRules?: MlAnomalyDetectionAlertRule[];
jobTags: Record<string, string>;
}

export interface AuditMessage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,33 @@ export function extractJobDetails(job, basePath, refreshJobList) {
items: filterObjects(job, true).map(formatValues),
};

const { job_tags: tags, custom_urls: urls, ...settings } = job.custom_settings ?? {};
const customUrl = {
id: 'customUrl',
title: i18n.translate('xpack.ml.jobsList.jobDetails.customUrlsTitle', {
defaultMessage: 'Custom URLs',
}),
position: 'right',
items: [],
items: urls ? urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) : [],
};

const customSettings = {
id: 'analysisConfig',
title: i18n.translate('xpack.ml.jobsList.jobDetails.customSettingsTitle', {
defaultMessage: 'Custom settings',
}),
position: 'right',
items: settings ? filterObjects(settings, true, true) : [],
};

const jobTags = {
id: 'analysisConfig',
title: i18n.translate('xpack.ml.jobsList.jobDetails.jobTagsTitle', {
defaultMessage: 'Job tags',
}),
position: 'right',
items: tags ? filterObjects(tags) : [],
};
if (job.custom_settings && job.custom_settings.custom_urls) {
customUrl.items.push(
...job.custom_settings.custom_urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range])
);
}

const node = {
id: 'node',
Expand Down Expand Up @@ -213,6 +227,8 @@ export function extractJobDetails(job, basePath, refreshJobList) {
analysisConfig,
analysisLimits,
dataDescription,
customSettings,
jobTags,
datafeed,
counts,
modelSizeStats,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class JobDetailsUI extends Component {
}

render() {
const { job } = this.state;
const job = this.state.job ?? this.props.job;
const {
services: {
http: { basePath },
Expand All @@ -67,6 +67,8 @@ export class JobDetailsUI extends Component {
analysisConfig,
analysisLimits,
dataDescription,
customSettings,
jobTags,
datafeed,
counts,
modelSizeStats,
Expand All @@ -85,7 +87,7 @@ export class JobDetailsUI extends Component {
content: (
<JobDetailsPane
data-test-subj="mlJobDetails-job-settings"
sections={[general, customUrl, node, calendars, alertRules]}
sections={[general, customSettings, customUrl, jobTags, node, calendars, alertRules]}
/>
),
time: job.open_time,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { isEqual } from 'lodash';
import { isEqual, debounce } from 'lodash';

import { ml } from '../../../../services/ml_api_service';
import { checkForAutoStartDatafeed, filterJobs, loadFullJob } from '../utils';
Expand All @@ -43,6 +43,11 @@ import { JobListMlAnomalyAlertFlyout } from '../../../../../alerting/ml_alerting

let deletingJobsRefreshTimeout = null;

const filterJobsDebounce = debounce((jobsSummaryList, filterClauses, callback) => {
const ss = filterJobs(jobsSummaryList, filterClauses);
callback(ss);
}, 500);

// 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page
export class JobsListView extends Component {
constructor(props) {
Expand Down Expand Up @@ -221,7 +226,7 @@ export class JobsListView extends Component {

refreshSelectedJobs() {
const selectedJobsIds = this.state.selectedJobs.map((j) => j.id);
const filteredJobIds = this.state.filteredJobsSummaryList.map((j) => j.id);
const filteredJobIds = (this.state.filteredJobsSummaryList ?? []).map((j) => j.id);

// refresh the jobs stored as selected
// only select those which are also in the filtered list
Expand All @@ -232,9 +237,17 @@ export class JobsListView extends Component {
this.setState({ selectedJobs });
}

setFilters = (query) => {
const filterClauses = (query && query.ast && query.ast.clauses) || [];
const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses);
setFilters = async (query) => {
if (query === null) {
this.setState(
{ filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses: [] },
() => {
this.refreshSelectedJobs();
}
);

return;
}

this.props.onJobsViewStateUpdate(
{
Expand All @@ -244,11 +257,30 @@ export class JobsListView extends Component {
this._isFiltersSet === false
);

this._isFiltersSet = true;
const filterClauses = (query && query.ast && query.ast.clauses) || [];

this.setState({ filteredJobsSummaryList, filterClauses }, () => {
this.refreshSelectedJobs();
});
if (filterClauses.length === 0) {
this.setState({ filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses }, () => {
this.refreshSelectedJobs();
});
return;
}

if (this._isFiltersSet === true) {
filterJobsDebounce(this.state.jobsSummaryList, filterClauses, (jobsSummaryList) => {
this.setState({ filteredJobsSummaryList: jobsSummaryList, filterClauses }, () => {
this.refreshSelectedJobs();
});
});
} else {
// first use after page load, do not debounce.
const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses);
this.setState({ filteredJobsSummaryList, filterClauses }, () => {
this.refreshSelectedJobs();
});
}

this._isFiltersSet = true;
};

onRefreshClick = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,18 @@ export function filterJobs(jobs, clauses) {
// if it's an array of job ids
if (c.field === 'id') {
js = jobs.filter((job) => c.value.indexOf(jobProperty(job, c.field)) >= 0);
} else {
} else if (c.field === 'groups') {
// the groups value is an array of group ids
js = jobs.filter((job) => jobProperty(job, c.field).some((g) => c.value.indexOf(g) >= 0));
} else if (c.field === 'job_tags') {
js = jobTagFilter(jobs, c.value);
}
} else {
js = jobs.filter((job) => jobProperty(job, c.field) === c.value);
if (c.field === 'job_tags') {
js = js = jobTagFilter(jobs, [c.value]);
} else {
js = jobs.filter((job) => jobProperty(job, c.field) === c.value);
}
}
}

Expand All @@ -369,6 +375,25 @@ export function filterJobs(jobs, clauses) {
return filteredJobs;
}

function jobProperty(job, prop) {
const propMap = {
job_state: 'jobState',
datafeed_state: 'datafeedState',
groups: 'groups',
id: 'id',
job_tags: 'jobTags',
};
return job[propMap[prop]];
}

function jobTagFilter(jobs, value) {
return jobs.filter((job) => {
const tags = jobProperty(job, 'job_tags');
return Object.entries(tags)
.map((t) => t.join(':'))
.find((t) => value.some((t1) => t1 === t));
});
}
// check to see if a job has been stored in mlJobService.tempJobCloningObjects
// if it has, return an object with the minimum properties needed for the
// start datafeed modal.
Expand All @@ -390,13 +415,3 @@ export function checkForAutoStartDatafeed() {
};
}
}

function jobProperty(job, prop) {
const propMap = {
job_state: 'jobState',
datafeed_state: 'datafeedState',
groups: 'groups',
id: 'id',
};
return job[propMap[prop]];
}
1 change: 1 addition & 0 deletions x-pack/plugins/ml/server/models/job_service/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export function jobsProvider(
deleting: job.deleting || undefined,
awaitingNodeAssignment: isJobAwaitingNodeAssignment(job),
alertingRules: job.alerting_rules,
jobTags: job.custom_settings?.job_tags ?? {},
};
if (jobIds.find((j) => j === tempJob.id)) {
tempJob.fullJob = job;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('useInstalledSecurityJobs', () => {
id: 'siem-api-rare_process_linux_ecs',
isSingleMetricViewerJob: true,
jobState: 'closed',
jobTags: {},
latestTimestampMs: 1557434782207,
memory_status: 'hard_limit',
processed_record_count: 582251,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const mockOpenedJob: MlSummaryJob = {
nodeName: 'siem-es',
processed_record_count: 3425264,
awaitingNodeAssignment: false,
jobTags: {},
};

export const mockJobsSummaryResponse: MlSummaryJob[] = [
Expand All @@ -67,6 +68,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
earliestTimestampMs: 1554327458406,
isSingleMetricViewerJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
{
id: 'siem-api-rare_process_linux_ecs',
Expand All @@ -83,6 +85,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
earliestTimestampMs: 1557353420495,
isSingleMetricViewerJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
{
id: 'siem-api-rare_process_windows_ecs',
Expand All @@ -97,6 +100,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
datafeedState: 'stopped',
isSingleMetricViewerJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
{
id: 'siem-api-suspicious_login_activity_ecs',
Expand All @@ -111,6 +115,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
datafeedState: 'stopped',
isSingleMetricViewerJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
];

Expand Down Expand Up @@ -520,6 +525,7 @@ export const mockSecurityJobs: SecurityJob[] = [
isInstalled: true,
isElasticJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
{
id: 'rare_process_by_host_linux_ecs',
Expand All @@ -539,6 +545,7 @@ export const mockSecurityJobs: SecurityJob[] = [
isInstalled: true,
isElasticJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
{
datafeedId: '',
Expand All @@ -558,5 +565,6 @@ export const mockSecurityJobs: SecurityJob[] = [
isInstalled: false,
isElasticJob: true,
awaitingNodeAssignment: false,
jobTags: {},
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('useSecurityJobs', () => {
isInstalled: true,
isSingleMetricViewerJob: true,
jobState: 'closed',
jobTags: {},
latestTimestampMs: 1557434782207,
memory_status: 'hard_limit',
moduleId: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('useSecurityJobsHelpers', () => {
isInstalled: false,
isSingleMetricViewerJob: false,
jobState: '',
jobTags: {},
memory_status: '',
moduleId: 'siem_auditbeat',
processed_record_count: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const moduleToSecurityJob = (
isInstalled: false,
isElasticJob: true,
awaitingNodeAssignment: false,
jobTags: {},
};
};

Expand Down

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

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

0 comments on commit 0ef1c3d

Please sign in to comment.