[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:
Felix Stürmer 2020-07-27 16:36:25 +02:00 committed by GitHub
parent 2a77307af1
commit aa45ac89b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 43 additions and 116 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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;
} }
} }

View file

@ -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({

View file

@ -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({

View file

@ -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),

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {