[Logs UI] Return empty result sets instead of 500 or 404 for analysis results (#72824)
This changes the analysis results routes to return empty result sets with HTTP status code 200 instead of and inconsistent status codes 500 or 404.
This commit is contained in:
parent
2a77307af1
commit
aa45ac89b0
|
@ -14,7 +14,6 @@ import {
|
||||||
logEntryDatasetsResponseRT,
|
logEntryDatasetsResponseRT,
|
||||||
} from './queries/log_entry_data_sets';
|
} from './queries/log_entry_data_sets';
|
||||||
import { decodeOrThrow } from '../../../common/runtime_types';
|
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||||
import { NoLogAnalysisResultsIndexError } from './errors';
|
|
||||||
import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing';
|
import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing';
|
||||||
|
|
||||||
export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: string) {
|
export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: string) {
|
||||||
|
@ -67,16 +66,8 @@ export async function getLogEntryDatasets(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (logEntryDatasetsResponse._shards.total === 0) {
|
const { after_key: afterKey, buckets: latestBatchBuckets = [] } =
|
||||||
throw new NoLogAnalysisResultsIndexError(
|
logEntryDatasetsResponse.aggregations?.dataset_buckets ?? {};
|
||||||
`Failed to find ml indices for jobs: ${jobIds.join(', ')}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
after_key: afterKey,
|
|
||||||
buckets: latestBatchBuckets,
|
|
||||||
} = logEntryDatasetsResponse.aggregations.dataset_buckets;
|
|
||||||
|
|
||||||
logEntryDatasetBuckets = [...logEntryDatasetBuckets, ...latestBatchBuckets];
|
logEntryDatasetBuckets = [...logEntryDatasetBuckets, ...latestBatchBuckets];
|
||||||
afterLatestBatchKey = afterKey;
|
afterLatestBatchKey = afterKey;
|
||||||
|
|
|
@ -6,13 +6,6 @@
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
export class NoLogAnalysisResultsIndexError extends Error {
|
|
||||||
constructor(message?: string) {
|
|
||||||
super(message);
|
|
||||||
Object.setPrototypeOf(this, new.target.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NoLogAnalysisMlJobError extends Error {
|
export class NoLogAnalysisMlJobError extends Error {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
|
@ -15,11 +15,7 @@ import {
|
||||||
import { startTracingSpan } from '../../../common/performance_tracing';
|
import { startTracingSpan } from '../../../common/performance_tracing';
|
||||||
import { decodeOrThrow } from '../../../common/runtime_types';
|
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||||
import type { MlAnomalyDetectors, MlSystem } from '../../types';
|
import type { MlAnomalyDetectors, MlSystem } from '../../types';
|
||||||
import {
|
import { InsufficientLogAnalysisMlJobConfigurationError, UnknownCategoryError } from './errors';
|
||||||
InsufficientLogAnalysisMlJobConfigurationError,
|
|
||||||
NoLogAnalysisResultsIndexError,
|
|
||||||
UnknownCategoryError,
|
|
||||||
} from './errors';
|
|
||||||
import {
|
import {
|
||||||
createLogEntryCategoriesQuery,
|
createLogEntryCategoriesQuery,
|
||||||
logEntryCategoriesResponseRT,
|
logEntryCategoriesResponseRT,
|
||||||
|
@ -235,38 +231,33 @@ async function fetchTopLogEntryCategories(
|
||||||
|
|
||||||
const esSearchSpan = finalizeEsSearchSpan();
|
const esSearchSpan = finalizeEsSearchSpan();
|
||||||
|
|
||||||
if (topLogEntryCategoriesResponse._shards.total === 0) {
|
const topLogEntryCategories =
|
||||||
throw new NoLogAnalysisResultsIndexError(
|
topLogEntryCategoriesResponse.aggregations?.terms_category_id.buckets.map(
|
||||||
`Failed to find ml result index for job ${logEntryCategoriesCountJobId}.`
|
(topCategoryBucket) => {
|
||||||
);
|
const maximumAnomalyScoresByDataset = topCategoryBucket.filter_record.terms_dataset.buckets.reduce<
|
||||||
}
|
Record<string, number>
|
||||||
|
>(
|
||||||
|
(accumulatedMaximumAnomalyScores, datasetFromRecord) => ({
|
||||||
|
...accumulatedMaximumAnomalyScores,
|
||||||
|
[datasetFromRecord.key]: datasetFromRecord.maximum_record_score.value ?? 0,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
const topLogEntryCategories = topLogEntryCategoriesResponse.aggregations.terms_category_id.buckets.map(
|
return {
|
||||||
(topCategoryBucket) => {
|
categoryId: parseCategoryId(topCategoryBucket.key),
|
||||||
const maximumAnomalyScoresByDataset = topCategoryBucket.filter_record.terms_dataset.buckets.reduce<
|
logEntryCount: topCategoryBucket.filter_model_plot.sum_actual.value ?? 0,
|
||||||
Record<string, number>
|
datasets: topCategoryBucket.filter_model_plot.terms_dataset.buckets
|
||||||
>(
|
.map((datasetBucket) => ({
|
||||||
(accumulatedMaximumAnomalyScores, datasetFromRecord) => ({
|
name: datasetBucket.key,
|
||||||
...accumulatedMaximumAnomalyScores,
|
maximumAnomalyScore: maximumAnomalyScoresByDataset[datasetBucket.key] ?? 0,
|
||||||
[datasetFromRecord.key]: datasetFromRecord.maximum_record_score.value ?? 0,
|
}))
|
||||||
}),
|
.sort(compareDatasetsByMaximumAnomalyScore)
|
||||||
{}
|
.reverse(),
|
||||||
);
|
maximumAnomalyScore: topCategoryBucket.filter_record.maximum_record_score.value ?? 0,
|
||||||
|
};
|
||||||
return {
|
}
|
||||||
categoryId: parseCategoryId(topCategoryBucket.key),
|
) ?? [];
|
||||||
logEntryCount: topCategoryBucket.filter_model_plot.sum_actual.value ?? 0,
|
|
||||||
datasets: topCategoryBucket.filter_model_plot.terms_dataset.buckets
|
|
||||||
.map((datasetBucket) => ({
|
|
||||||
name: datasetBucket.key,
|
|
||||||
maximumAnomalyScore: maximumAnomalyScoresByDataset[datasetBucket.key] ?? 0,
|
|
||||||
}))
|
|
||||||
.sort(compareDatasetsByMaximumAnomalyScore)
|
|
||||||
.reverse(),
|
|
||||||
maximumAnomalyScore: topCategoryBucket.filter_record.maximum_record_score.value ?? 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
topLogEntryCategories,
|
topLogEntryCategories,
|
||||||
|
|
|
@ -4,10 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { pipe } from 'fp-ts/lib/pipeable';
|
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||||
import { map, fold } from 'fp-ts/lib/Either';
|
|
||||||
import { identity } from 'fp-ts/lib/function';
|
|
||||||
import { throwErrors, createPlainError } from '../../../common/runtime_types';
|
|
||||||
import {
|
import {
|
||||||
logRateModelPlotResponseRT,
|
logRateModelPlotResponseRT,
|
||||||
createLogEntryRateQuery,
|
createLogEntryRateQuery,
|
||||||
|
@ -15,7 +12,6 @@ import {
|
||||||
CompositeTimestampPartitionKey,
|
CompositeTimestampPartitionKey,
|
||||||
} from './queries';
|
} from './queries';
|
||||||
import { getJobId } from '../../../common/log_analysis';
|
import { getJobId } from '../../../common/log_analysis';
|
||||||
import { NoLogAnalysisResultsIndexError } from './errors';
|
|
||||||
import type { MlSystem } from '../../types';
|
import type { MlSystem } from '../../types';
|
||||||
|
|
||||||
const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
|
const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
|
||||||
|
@ -50,22 +46,14 @@ export async function getLogEntryRateBuckets(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mlModelPlotResponse._shards.total === 0) {
|
const { after_key: afterKey, buckets: latestBatchBuckets = [] } =
|
||||||
throw new NoLogAnalysisResultsIndexError(
|
decodeOrThrow(logRateModelPlotResponseRT)(mlModelPlotResponse).aggregations
|
||||||
`Failed to query ml result index for job ${logRateJobId}.`
|
?.timestamp_partition_buckets ?? {};
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { after_key: afterKey, buckets: latestBatchBuckets } = pipe(
|
|
||||||
logRateModelPlotResponseRT.decode(mlModelPlotResponse),
|
|
||||||
map((response) => response.aggregations.timestamp_partition_buckets),
|
|
||||||
fold(throwErrors(createPlainError), identity)
|
|
||||||
);
|
|
||||||
|
|
||||||
mlModelPlotBuckets = [...mlModelPlotBuckets, ...latestBatchBuckets];
|
mlModelPlotBuckets = [...mlModelPlotBuckets, ...latestBatchBuckets];
|
||||||
afterLatestBatchKey = afterKey;
|
afterLatestBatchKey = afterKey;
|
||||||
|
|
||||||
if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) {
|
if (afterKey == null || latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ export type LogEntryDatasetBucket = rt.TypeOf<typeof logEntryDatasetBucketRT>;
|
||||||
|
|
||||||
export const logEntryDatasetsResponseRT = rt.intersection([
|
export const logEntryDatasetsResponseRT = rt.intersection([
|
||||||
commonSearchSuccessResponseFieldsRT,
|
commonSearchSuccessResponseFieldsRT,
|
||||||
rt.type({
|
rt.partial({
|
||||||
aggregations: rt.type({
|
aggregations: rt.type({
|
||||||
dataset_buckets: rt.intersection([
|
dataset_buckets: rt.intersection([
|
||||||
rt.type({
|
rt.type({
|
||||||
|
|
|
@ -162,7 +162,7 @@ export const logRateModelPlotBucketRT = rt.type({
|
||||||
|
|
||||||
export type LogRateModelPlotBucket = rt.TypeOf<typeof logRateModelPlotBucketRT>;
|
export type LogRateModelPlotBucket = rt.TypeOf<typeof logRateModelPlotBucketRT>;
|
||||||
|
|
||||||
export const logRateModelPlotResponseRT = rt.type({
|
export const logRateModelPlotResponseRT = rt.partial({
|
||||||
aggregations: rt.type({
|
aggregations: rt.type({
|
||||||
timestamp_partition_buckets: rt.intersection([
|
timestamp_partition_buckets: rt.intersection([
|
||||||
rt.type({
|
rt.type({
|
||||||
|
|
|
@ -159,7 +159,7 @@ export type LogEntryCategoryBucket = rt.TypeOf<typeof logEntryCategoryBucketRT>;
|
||||||
|
|
||||||
export const topLogEntryCategoriesResponseRT = rt.intersection([
|
export const topLogEntryCategoriesResponseRT = rt.intersection([
|
||||||
commonSearchSuccessResponseFieldsRT,
|
commonSearchSuccessResponseFieldsRT,
|
||||||
rt.type({
|
rt.partial({
|
||||||
aggregations: rt.type({
|
aggregations: rt.type({
|
||||||
terms_category_id: rt.type({
|
terms_category_id: rt.type({
|
||||||
buckets: rt.array(logEntryCategoryBucketRT),
|
buckets: rt.array(logEntryCategoryBucketRT),
|
||||||
|
|
|
@ -12,10 +12,7 @@ import {
|
||||||
} from '../../../../common/http_api/log_analysis';
|
} from '../../../../common/http_api/log_analysis';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
||||||
import {
|
import { getLogEntryAnomaliesDatasets } from '../../../lib/log_analysis';
|
||||||
getLogEntryAnomaliesDatasets,
|
|
||||||
NoLogAnalysisResultsIndexError,
|
|
||||||
} from '../../../lib/log_analysis';
|
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBackendLibs) => {
|
export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBackendLibs) => {
|
||||||
|
@ -58,10 +55,6 @@ export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBacken
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -12,10 +12,7 @@ import {
|
||||||
} from '../../../../common/http_api/log_analysis';
|
} from '../../../../common/http_api/log_analysis';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
||||||
import {
|
import { getTopLogEntryCategories } from '../../../lib/log_analysis';
|
||||||
getTopLogEntryCategories,
|
|
||||||
NoLogAnalysisResultsIndexError,
|
|
||||||
} from '../../../lib/log_analysis';
|
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) => {
|
export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) => {
|
||||||
|
@ -69,10 +66,6 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs)
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -12,10 +12,7 @@ import {
|
||||||
} from '../../../../common/http_api/log_analysis';
|
} from '../../../../common/http_api/log_analysis';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
||||||
import {
|
import { getLogEntryCategoryDatasets } from '../../../lib/log_analysis';
|
||||||
getLogEntryCategoryDatasets,
|
|
||||||
NoLogAnalysisResultsIndexError,
|
|
||||||
} from '../../../lib/log_analysis';
|
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackendLibs) => {
|
export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackendLibs) => {
|
||||||
|
@ -58,10 +55,6 @@ export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackend
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -12,10 +12,7 @@ import {
|
||||||
} from '../../../../common/http_api/log_analysis';
|
} from '../../../../common/http_api/log_analysis';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
import type { InfraBackendLibs } from '../../../lib/infra_types';
|
||||||
import {
|
import { getLogEntryCategoryExamples } from '../../../lib/log_analysis';
|
||||||
getLogEntryCategoryExamples,
|
|
||||||
NoLogAnalysisResultsIndexError,
|
|
||||||
} from '../../../lib/log_analysis';
|
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryCategoryExamplesRoute = ({ framework, sources }: InfraBackendLibs) => {
|
export const initGetLogEntryCategoryExamplesRoute = ({ framework, sources }: InfraBackendLibs) => {
|
||||||
|
@ -68,10 +65,6 @@ export const initGetLogEntryCategoryExamplesRoute = ({ framework, sources }: Inf
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import Boom from 'boom';
|
import Boom from 'boom';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import { InfraBackendLibs } from '../../../lib/infra_types';
|
import { InfraBackendLibs } from '../../../lib/infra_types';
|
||||||
import { NoLogAnalysisResultsIndexError, getLogEntryExamples } from '../../../lib/log_analysis';
|
import { getLogEntryExamples } from '../../../lib/log_analysis';
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
import {
|
import {
|
||||||
getLogEntryExamplesRequestPayloadRT,
|
getLogEntryExamplesRequestPayloadRT,
|
||||||
|
@ -68,10 +68,6 @@ export const initGetLogEntryExamplesRoute = ({ framework, sources }: InfraBacken
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
GetLogEntryRateSuccessResponsePayload,
|
GetLogEntryRateSuccessResponsePayload,
|
||||||
} from '../../../../common/http_api/log_analysis';
|
} from '../../../../common/http_api/log_analysis';
|
||||||
import { createValidationFunction } from '../../../../common/runtime_types';
|
import { createValidationFunction } from '../../../../common/runtime_types';
|
||||||
import { NoLogAnalysisResultsIndexError, getLogEntryRateBuckets } from '../../../lib/log_analysis';
|
import { getLogEntryRateBuckets } from '../../../lib/log_analysis';
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryRateRoute = ({ framework }: InfraBackendLibs) => {
|
export const initGetLogEntryRateRoute = ({ framework }: InfraBackendLibs) => {
|
||||||
|
@ -56,10 +56,6 @@ export const initGetLogEntryRateRoute = ({ framework }: InfraBackendLibs) => {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NoLogAnalysisResultsIndexError) {
|
|
||||||
return response.notFound({ body: { message: error.message } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: error.statusCode ?? 500,
|
statusCode: error.statusCode ?? 500,
|
||||||
body: {
|
body: {
|
||||||
|
|
Loading…
Reference in a new issue