-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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] Switching to new datafeed preview #101780
Changes from 2 commits
6ff9693
c388572
f895bb1
2785118
e4bb257
7392692
a1c9ab8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,10 +17,8 @@ import { parseInterval } from '../../../../../common/util/parse_interval'; | |
import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils'; | ||
import { getIndexPatternIdFromName } from '../../../util/index_utils'; | ||
import { ml } from '../../../services/ml_api_service'; | ||
import { mlJobService } from '../../../services/job_service'; | ||
import { escapeForElasticsearchQuery } from '../../../util/string_utils'; | ||
import { getSavedObjectsClient, getGetUrlGenerator } from '../../../util/dependency_cache'; | ||
import { getProcessedFields } from '../../../components/data_grid'; | ||
|
||
export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { | ||
// Returns the settings object in the format used by the custom URL editor | ||
|
@@ -266,8 +264,7 @@ function buildAppStateQueryParam(queryFieldNames) { | |
// Builds the full URL for testing out a custom URL configuration, which | ||
// may contain dollar delimited partition / influencer entity tokens and | ||
// drilldown time range settings. | ||
export function getTestUrl(job, customUrl) { | ||
const urlValue = customUrl.url_value; | ||
export async function getTestUrl(job, customUrl) { | ||
const bucketSpanSecs = parseInterval(job.analysis_config.bucket_span).asSeconds(); | ||
|
||
// By default, return configured url_value. Look to substitute any dollar-delimited | ||
|
@@ -289,64 +286,53 @@ export function getTestUrl(job, customUrl) { | |
sort: [{ record_score: { order: 'desc' } }], | ||
}; | ||
|
||
return new Promise((resolve, reject) => { | ||
ml.results | ||
.anomalySearch( | ||
{ | ||
body, | ||
}, | ||
[job.job_id] | ||
) | ||
.then((resp) => { | ||
if (resp.hits.total.value > 0) { | ||
const record = resp.hits.hits[0]._source; | ||
testUrl = replaceTokensInUrlValue(customUrl, bucketSpanSecs, record, 'timestamp'); | ||
resolve(testUrl); | ||
} else { | ||
// No anomalies yet for this job, so do a preview of the search | ||
// configured in the job datafeed to obtain sample docs. | ||
mlJobService.searchPreview(job).then((response) => { | ||
let testDoc; | ||
const docTimeFieldName = job.data_description.time_field; | ||
|
||
// Handle datafeeds which use aggregations or documents. | ||
if (response.aggregations) { | ||
// Create a dummy object which contains the fields necessary to build the URL. | ||
const firstBucket = response.aggregations.buckets.buckets[0]; | ||
testDoc = { | ||
[docTimeFieldName]: firstBucket.key, | ||
}; | ||
|
||
// Look for bucket aggregations which match the tokens in the URL. | ||
urlValue.replace(/\$([^?&$\'"]{1,40})\$/g, (match, name) => { | ||
if (name !== 'earliest' && name !== 'latest' && firstBucket[name] !== undefined) { | ||
const tokenBuckets = firstBucket[name]; | ||
if (tokenBuckets.buckets) { | ||
testDoc[name] = tokenBuckets.buckets[0].key; | ||
} | ||
} | ||
}); | ||
} else { | ||
if (response.hits.total.value > 0) { | ||
testDoc = getProcessedFields(response.hits.hits[0].fields); | ||
} | ||
} | ||
|
||
if (testDoc !== undefined) { | ||
testUrl = replaceTokensInUrlValue( | ||
customUrl, | ||
bucketSpanSecs, | ||
testDoc, | ||
docTimeFieldName | ||
); | ||
} | ||
let resp; | ||
try { | ||
resp = await ml.results.anomalySearch( | ||
{ | ||
body, | ||
}, | ||
[job.job_id] | ||
); | ||
} catch (error) { | ||
// search may fail if the job doesn't already exist | ||
} | ||
|
||
resolve(testUrl); | ||
}); | ||
} | ||
}) | ||
.catch((resp) => { | ||
reject(resp); | ||
}); | ||
}); | ||
if (resp && resp.hits.total.value > 0) { | ||
const record = resp.hits.hits[0]._source; | ||
testUrl = replaceTokensInUrlValue(customUrl, bucketSpanSecs, record, 'timestamp'); | ||
return testUrl; | ||
} else { | ||
// No anomalies yet for this job, so do a preview of the search | ||
// configured in the job datafeed to obtain sample docs. | ||
|
||
let { datafeed_config: datafeedConfig, ...jobConfig } = job; | ||
try { | ||
// attempt load the non-combined job and datafeed so they can be used in the datafeed preview | ||
const [{ jobs }, { datafeeds }] = await Promise.all([ | ||
ml.getJobs({ jobId: job.job_id }), | ||
ml.getDatafeeds({ datafeedId: job.datafeed_config.datafeed_id }), | ||
]); | ||
datafeedConfig = datafeeds[0]; | ||
jobConfig = jobs[0]; | ||
} catch (error) { | ||
// jobs may not exist as this might be called from the AD job wizards | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to check for a particular error code/message here? otherwise, it might silently fail unintentionally. |
||
|
||
if (jobConfig === undefined || datafeedConfig === undefined) { | ||
return testUrl; | ||
} | ||
|
||
const preview = await ml.jobs.datafeedPreview(undefined, jobConfig, datafeedConfig); | ||
|
||
const docTimeFieldName = job.data_description.time_field; | ||
|
||
// Create a dummy object which contains the fields necessary to build the URL. | ||
const firstBucket = preview[0]; | ||
if (firstBucket !== undefined) { | ||
testUrl = replaceTokensInUrlValue(customUrl, bucketSpanSecs, firstBucket, docTimeFieldName); | ||
} | ||
|
||
return testUrl; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,15 +19,15 @@ import { | |
|
||
import { CombinedJob } from '../../../../../../../../common/types/anomaly_detection_jobs'; | ||
import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; | ||
import { mlJobService } from '../../../../../../services/job_service'; | ||
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils'; | ||
import { isPopulatedObject } from '../../../../../../../../common/util/object_utils'; | ||
import { isMultiBucketAggregate } from '../../../../../../../../common/types/es_client'; | ||
import { useMlApiContext } from '../../../../../../contexts/kibana'; | ||
|
||
export const DatafeedPreview: FC<{ | ||
combinedJob: CombinedJob | null; | ||
heightOffset?: number; | ||
}> = ({ combinedJob, heightOffset = 0 }) => { | ||
const { | ||
jobs: { datafeedPreview }, | ||
} = useMlApiContext(); | ||
// the ace editor requires a fixed height | ||
const editorHeight = useMemo(() => `${window.innerHeight - 230 - heightOffset}px`, [ | ||
heightOffset, | ||
|
@@ -63,18 +63,9 @@ export const DatafeedPreview: FC<{ | |
|
||
if (combinedJob.datafeed_config && combinedJob.datafeed_config.indices.length) { | ||
try { | ||
const resp = await mlJobService.searchPreview(combinedJob); | ||
let data = resp.hits.hits; | ||
// the first item under aggregations can be any name | ||
if (isPopulatedObject(resp.aggregations)) { | ||
const accessor = Object.keys(resp.aggregations)[0]; | ||
const aggregate = resp.aggregations[accessor]; | ||
if (isMultiBucketAggregate(aggregate)) { | ||
data = aggregate.buckets.slice(0, ML_DATA_PREVIEW_COUNT); | ||
} | ||
} | ||
|
||
setPreviewJsonString(JSON.stringify(data, null, 2)); | ||
const { datafeed_config: datafeed, ...job } = combinedJob; | ||
const preview = await datafeedPreview(undefined, job, datafeed); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
setPreviewJsonString(JSON.stringify(preview, null, 2)); | ||
} catch (error) { | ||
setPreviewJsonString(JSON.stringify(error, null, 2)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to check for a particular error code/message here? otherwise it might silently fail unintentionally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Failures here will cause failures later on which will be caught by the calling function and displayed to the user in a toast.
I also am not sure what errors we'd need to check for and accept.
This is also a feature that isn't used that much. It has been broken for a long time and no one has noticed.