mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Logs UI] Store Logs UI settings in a dedicated infrastructure-monitoring-log-view
saved object (#125014)
This commit is contained in:
parent
30ca2f5c50
commit
a736c44e21
165 changed files with 3783 additions and 2771 deletions
|
@ -56,6 +56,7 @@ const previouslyRegisteredTypes = [
|
|||
'fleet-preconfiguration-deletion-record',
|
||||
'graph-workspace',
|
||||
'index-pattern',
|
||||
'infrastructure-monitoring-log-view',
|
||||
'infrastructure-ui-source',
|
||||
'ingest-agent-policies',
|
||||
'ingest-outputs',
|
||||
|
|
14
x-pack/plugins/infra/.storybook/preview.js
Normal file
14
x-pack/plugins/infra/.storybook/preview.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
source: {
|
||||
type: 'code', // without this, stories in mdx documents freeze the browser
|
||||
},
|
||||
},
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import * as rt from 'io-ts';
|
||||
import { logEntryCursorRT, logEntryRT } from '../../log_entry';
|
||||
import { logSourceColumnConfigurationRT } from '../../log_sources/log_source_configuration';
|
||||
import { logViewColumnConfigurationRT } from '../../log_views';
|
||||
|
||||
export const LOG_ENTRIES_HIGHLIGHTS_PATH = '/api/log_entries/highlights';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export const logEntriesHighlightsBaseRequestRT = rt.intersection([
|
|||
rt.partial({
|
||||
query: rt.union([rt.string, rt.null]),
|
||||
size: rt.number,
|
||||
columns: rt.array(logSourceColumnConfigurationRT),
|
||||
columns: rt.array(logViewColumnConfigurationRT),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const LOG_SOURCE_CONFIGURATION_PATH_PREFIX = '/api/infra/log_source_configurations';
|
||||
export const LOG_SOURCE_CONFIGURATION_PATH = `${LOG_SOURCE_CONFIGURATION_PATH_PREFIX}/{sourceId}`;
|
||||
export const getLogSourceConfigurationPath = (sourceId: string) =>
|
||||
`${LOG_SOURCE_CONFIGURATION_PATH_PREFIX}/${sourceId}`;
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { badRequestErrorRT, forbiddenErrorRT, routeTimingMetadataRT } from '../shared';
|
||||
import { logSourceConfigurationRT } from '../../log_sources/log_source_configuration';
|
||||
|
||||
/**
|
||||
* request
|
||||
*/
|
||||
|
||||
export const getLogSourceConfigurationRequestParamsRT = rt.type({
|
||||
// the id of the source configuration
|
||||
sourceId: rt.string,
|
||||
});
|
||||
|
||||
export type GetLogSourceConfigurationRequestParams = rt.TypeOf<
|
||||
typeof getLogSourceConfigurationRequestParamsRT
|
||||
>;
|
||||
|
||||
/**
|
||||
* response
|
||||
*/
|
||||
|
||||
export const getLogSourceConfigurationSuccessResponsePayloadRT = rt.intersection([
|
||||
rt.type({
|
||||
data: logSourceConfigurationRT,
|
||||
}),
|
||||
rt.partial({
|
||||
timing: routeTimingMetadataRT,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type GetLogSourceConfigurationSuccessResponsePayload = rt.TypeOf<
|
||||
typeof getLogSourceConfigurationSuccessResponsePayloadRT
|
||||
>;
|
||||
|
||||
export const getLogSourceConfigurationErrorResponsePayloadRT = rt.union([
|
||||
badRequestErrorRT,
|
||||
forbiddenErrorRT,
|
||||
]);
|
||||
|
||||
export type GetLogSourceConfigurationErrorReponsePayload = rt.TypeOf<
|
||||
typeof getLogSourceConfigurationErrorResponsePayloadRT
|
||||
>;
|
||||
|
||||
export const getLogSourceConfigurationResponsePayloadRT = rt.union([
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
getLogSourceConfigurationErrorResponsePayloadRT,
|
||||
]);
|
||||
|
||||
export type GetLogSourceConfigurationReponsePayload = rt.TypeOf<
|
||||
typeof getLogSourceConfigurationResponsePayloadRT
|
||||
>;
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { routeTimingMetadataRT } from '../shared';
|
||||
import { getLogSourceConfigurationPath, LOG_SOURCE_CONFIGURATION_PATH } from './common';
|
||||
|
||||
export const LOG_SOURCE_STATUS_PATH_SUFFIX = 'status';
|
||||
export const LOG_SOURCE_STATUS_PATH = `${LOG_SOURCE_CONFIGURATION_PATH}/${LOG_SOURCE_STATUS_PATH_SUFFIX}`;
|
||||
export const getLogSourceStatusPath = (sourceId: string) =>
|
||||
`${getLogSourceConfigurationPath(sourceId)}/${LOG_SOURCE_STATUS_PATH_SUFFIX}`;
|
||||
|
||||
/**
|
||||
* request
|
||||
*/
|
||||
|
||||
export const getLogSourceStatusRequestParamsRT = rt.type({
|
||||
// the id of the source configuration
|
||||
sourceId: rt.string,
|
||||
});
|
||||
|
||||
export type GetLogSourceStatusRequestParams = rt.TypeOf<typeof getLogSourceStatusRequestParamsRT>;
|
||||
|
||||
/**
|
||||
* response
|
||||
*/
|
||||
|
||||
const logIndexFieldRT = rt.strict({
|
||||
name: rt.string,
|
||||
type: rt.string,
|
||||
searchable: rt.boolean,
|
||||
aggregatable: rt.boolean,
|
||||
});
|
||||
|
||||
export type LogIndexField = rt.TypeOf<typeof logIndexFieldRT>;
|
||||
|
||||
const logIndexStatusRT = rt.keyof({
|
||||
missing: null,
|
||||
empty: null,
|
||||
available: null,
|
||||
});
|
||||
|
||||
export type LogIndexStatus = rt.TypeOf<typeof logIndexStatusRT>;
|
||||
|
||||
const logSourceStatusRT = rt.strict({
|
||||
logIndexStatus: logIndexStatusRT,
|
||||
indices: rt.string,
|
||||
});
|
||||
|
||||
export type LogSourceStatus = rt.TypeOf<typeof logSourceStatusRT>;
|
||||
|
||||
export const getLogSourceStatusSuccessResponsePayloadRT = rt.intersection([
|
||||
rt.type({
|
||||
data: logSourceStatusRT,
|
||||
}),
|
||||
rt.partial({
|
||||
timing: routeTimingMetadataRT,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type GetLogSourceStatusSuccessResponsePayload = rt.TypeOf<
|
||||
typeof getLogSourceStatusSuccessResponsePayloadRT
|
||||
>;
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { badRequestErrorRT, forbiddenErrorRT } from '../shared';
|
||||
import { getLogSourceConfigurationSuccessResponsePayloadRT } from './get_log_source_configuration';
|
||||
import { logSourceConfigurationPropertiesRT } from '../../log_sources/log_source_configuration';
|
||||
|
||||
/**
|
||||
* request
|
||||
*/
|
||||
|
||||
export const patchLogSourceConfigurationRequestParamsRT = rt.type({
|
||||
// the id of the source configuration
|
||||
sourceId: rt.string,
|
||||
});
|
||||
|
||||
export type PatchLogSourceConfigurationRequestParams = rt.TypeOf<
|
||||
typeof patchLogSourceConfigurationRequestParamsRT
|
||||
>;
|
||||
|
||||
const logSourceConfigurationProperiesPatchRT = rt.partial({
|
||||
...logSourceConfigurationPropertiesRT.type.props,
|
||||
fields: rt.partial(logSourceConfigurationPropertiesRT.type.props.fields.type.props),
|
||||
});
|
||||
|
||||
export type LogSourceConfigurationPropertiesPatch = rt.TypeOf<
|
||||
typeof logSourceConfigurationProperiesPatchRT
|
||||
>;
|
||||
|
||||
export const patchLogSourceConfigurationRequestBodyRT = rt.type({
|
||||
data: logSourceConfigurationProperiesPatchRT,
|
||||
});
|
||||
|
||||
export type PatchLogSourceConfigurationRequestBody = rt.TypeOf<
|
||||
typeof patchLogSourceConfigurationRequestBodyRT
|
||||
>;
|
||||
|
||||
/**
|
||||
* response
|
||||
*/
|
||||
|
||||
export const patchLogSourceConfigurationSuccessResponsePayloadRT =
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT;
|
||||
|
||||
export type PatchLogSourceConfigurationSuccessResponsePayload = rt.TypeOf<
|
||||
typeof patchLogSourceConfigurationSuccessResponsePayloadRT
|
||||
>;
|
||||
|
||||
export const patchLogSourceConfigurationResponsePayloadRT = rt.union([
|
||||
patchLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
badRequestErrorRT,
|
||||
forbiddenErrorRT,
|
||||
]);
|
||||
|
||||
export type PatchLogSourceConfigurationReponsePayload = rt.TypeOf<
|
||||
typeof patchLogSourceConfigurationResponsePayloadRT
|
||||
>;
|
10
x-pack/plugins/infra/common/http_api/log_views/common.ts
Normal file
10
x-pack/plugins/infra/common/http_api/log_views/common.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const LOG_VIEW_URL_PREFIX = '/api/infra/log_views';
|
||||
export const LOG_VIEW_URL = `${LOG_VIEW_URL_PREFIX}/{logViewId}`;
|
||||
export const getLogViewUrl = (logViewId: string) => `${LOG_VIEW_URL_PREFIX}/${logViewId}`;
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { logViewRT } from '../../log_views';
|
||||
|
||||
export const getLogViewRequestParamsRT = rt.type({
|
||||
// the id of the log view
|
||||
logViewId: rt.string,
|
||||
});
|
||||
|
||||
export const getLogViewResponsePayloadRT = rt.type({
|
||||
data: logViewRT,
|
||||
});
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './get_log_source_configuration';
|
||||
export * from './get_log_source_status';
|
||||
export * from './patch_log_source_configuration';
|
||||
export * from './common';
|
||||
export { getLogViewUrl, LOG_VIEW_URL } from './common';
|
||||
export * from './get_log_view';
|
||||
export * from './put_log_view';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { logViewAttributesRT, logViewRT } from '../../log_views';
|
||||
|
||||
export const putLogViewRequestParamsRT = rt.type({
|
||||
logViewId: rt.string,
|
||||
});
|
||||
|
||||
export const putLogViewRequestPayloadRT = rt.type({
|
||||
attributes: rt.partial(logViewAttributesRT.type.props),
|
||||
});
|
||||
export type PutLogViewRequestPayload = rt.TypeOf<typeof putLogViewRequestPayloadRT>;
|
||||
|
||||
export const putLogViewResponsePayloadRT = rt.type({
|
||||
data: logViewRT,
|
||||
});
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export const logSourceConfigurationOriginRT = rt.keyof({
|
||||
fallback: null,
|
||||
internal: null,
|
||||
stored: null,
|
||||
});
|
||||
|
||||
export type LogSourceConfigurationOrigin = rt.TypeOf<typeof logSourceConfigurationOriginRT>;
|
||||
|
||||
const logSourceFieldsConfigurationRT = rt.strict({
|
||||
message: rt.array(rt.string),
|
||||
});
|
||||
|
||||
const logSourceCommonColumnConfigurationRT = rt.strict({
|
||||
id: rt.string,
|
||||
});
|
||||
|
||||
const logSourceTimestampColumnConfigurationRT = rt.strict({
|
||||
timestampColumn: logSourceCommonColumnConfigurationRT,
|
||||
});
|
||||
|
||||
const logSourceMessageColumnConfigurationRT = rt.strict({
|
||||
messageColumn: logSourceCommonColumnConfigurationRT,
|
||||
});
|
||||
|
||||
export const logSourceFieldColumnConfigurationRT = rt.strict({
|
||||
fieldColumn: rt.intersection([
|
||||
logSourceCommonColumnConfigurationRT,
|
||||
rt.strict({
|
||||
field: rt.string,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const logSourceColumnConfigurationRT = rt.union([
|
||||
logSourceTimestampColumnConfigurationRT,
|
||||
logSourceMessageColumnConfigurationRT,
|
||||
logSourceFieldColumnConfigurationRT,
|
||||
]);
|
||||
export type LogSourceColumnConfiguration = rt.TypeOf<typeof logSourceColumnConfigurationRT>;
|
||||
|
||||
// Kibana index pattern
|
||||
export const logIndexPatternReferenceRT = rt.type({
|
||||
type: rt.literal('index_pattern'),
|
||||
indexPatternId: rt.string,
|
||||
});
|
||||
export type LogIndexPatternReference = rt.TypeOf<typeof logIndexPatternReferenceRT>;
|
||||
|
||||
// Legacy support
|
||||
export const logIndexNameReferenceRT = rt.type({
|
||||
type: rt.literal('index_name'),
|
||||
indexName: rt.string,
|
||||
});
|
||||
export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>;
|
||||
|
||||
export const logIndexReferenceRT = rt.union([logIndexPatternReferenceRT, logIndexNameReferenceRT]);
|
||||
export type LogIndexReference = rt.TypeOf<typeof logIndexReferenceRT>;
|
||||
|
||||
export const logSourceConfigurationPropertiesRT = rt.strict({
|
||||
name: rt.string,
|
||||
description: rt.string,
|
||||
logIndices: logIndexReferenceRT,
|
||||
fields: logSourceFieldsConfigurationRT,
|
||||
logColumns: rt.array(logSourceColumnConfigurationRT),
|
||||
});
|
||||
|
||||
export type LogSourceConfigurationProperties = rt.TypeOf<typeof logSourceConfigurationPropertiesRT>;
|
||||
|
||||
export const logSourceConfigurationRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
origin: logSourceConfigurationOriginRT,
|
||||
configuration: logSourceConfigurationPropertiesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
export type LogSourceConfiguration = rt.TypeOf<typeof logSourceConfigurationRT>;
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { DataView, DataViewsContract } from '../../../../../src/plugins/data_views/common';
|
||||
import { TIMESTAMP_FIELD, TIEBREAKER_FIELD } from '../constants';
|
||||
import { ResolveLogSourceConfigurationError } from './errors';
|
||||
import {
|
||||
LogSourceColumnConfiguration,
|
||||
LogSourceConfigurationProperties,
|
||||
} from './log_source_configuration';
|
||||
|
||||
export interface ResolvedLogSourceConfiguration {
|
||||
name: string;
|
||||
description: string;
|
||||
indices: string;
|
||||
timestampField: string;
|
||||
tiebreakerField: string;
|
||||
messageField: string[];
|
||||
fields: DataView['fields'];
|
||||
runtimeMappings: estypes.MappingRuntimeFields;
|
||||
columns: LogSourceColumnConfiguration[];
|
||||
}
|
||||
|
||||
export const resolveLogSourceConfiguration = async (
|
||||
sourceConfiguration: LogSourceConfigurationProperties,
|
||||
indexPatternsService: DataViewsContract
|
||||
): Promise<ResolvedLogSourceConfiguration> => {
|
||||
if (sourceConfiguration.logIndices.type === 'index_name') {
|
||||
return await resolveLegacyReference(sourceConfiguration, indexPatternsService);
|
||||
} else {
|
||||
return await resolveKibanaIndexPatternReference(sourceConfiguration, indexPatternsService);
|
||||
}
|
||||
};
|
||||
|
||||
const resolveLegacyReference = async (
|
||||
sourceConfiguration: LogSourceConfigurationProperties,
|
||||
indexPatternsService: DataViewsContract
|
||||
): Promise<ResolvedLogSourceConfiguration> => {
|
||||
if (sourceConfiguration.logIndices.type !== 'index_name') {
|
||||
throw new Error('This function can only resolve legacy references');
|
||||
}
|
||||
|
||||
const indices = sourceConfiguration.logIndices.indexName;
|
||||
|
||||
const fields = await indexPatternsService
|
||||
.getFieldsForWildcard({
|
||||
pattern: indices,
|
||||
allowNoIndex: true,
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new ResolveLogSourceConfigurationError(
|
||||
`Failed to fetch fields for indices "${indices}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: sourceConfiguration.logIndices.indexName,
|
||||
timestampField: TIMESTAMP_FIELD,
|
||||
tiebreakerField: TIEBREAKER_FIELD,
|
||||
messageField: sourceConfiguration.fields.message,
|
||||
// @ts-ignore
|
||||
fields,
|
||||
runtimeMappings: {},
|
||||
columns: sourceConfiguration.logColumns,
|
||||
name: sourceConfiguration.name,
|
||||
description: sourceConfiguration.description,
|
||||
};
|
||||
};
|
||||
|
||||
const resolveKibanaIndexPatternReference = async (
|
||||
sourceConfiguration: LogSourceConfigurationProperties,
|
||||
indexPatternsService: DataViewsContract
|
||||
): Promise<ResolvedLogSourceConfiguration> => {
|
||||
if (sourceConfiguration.logIndices.type !== 'index_pattern') {
|
||||
throw new Error('This function can only resolve Kibana Index Pattern references');
|
||||
}
|
||||
|
||||
const { indexPatternId } = sourceConfiguration.logIndices;
|
||||
|
||||
const indexPattern = await indexPatternsService.get(indexPatternId).catch((error) => {
|
||||
throw new ResolveLogSourceConfigurationError(
|
||||
`Failed to fetch index pattern "${indexPatternId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: indexPattern.title,
|
||||
timestampField: indexPattern.timeFieldName ?? TIMESTAMP_FIELD,
|
||||
tiebreakerField: TIEBREAKER_FIELD,
|
||||
messageField: ['message'],
|
||||
fields: indexPattern.fields,
|
||||
runtimeMappings: resolveRuntimeMappings(indexPattern),
|
||||
columns: sourceConfiguration.logColumns,
|
||||
name: sourceConfiguration.name,
|
||||
description: sourceConfiguration.description,
|
||||
};
|
||||
};
|
||||
|
||||
// this might take other sources of runtime fields into account in the future
|
||||
const resolveRuntimeMappings = (indexPattern: DataView): estypes.MappingRuntimeFields => {
|
||||
return indexPattern.getRuntimeMappings();
|
||||
};
|
42
x-pack/plugins/infra/common/log_views/defaults.ts
Normal file
42
x-pack/plugins/infra/common/log_views/defaults.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { defaultSourceConfiguration } from '../source_configuration/defaults';
|
||||
import { LogViewAttributes, LogViewsStaticConfig } from './types';
|
||||
|
||||
export const defaultLogViewId = 'default';
|
||||
|
||||
export const defaultLogViewAttributes: LogViewAttributes = {
|
||||
name: 'Log View',
|
||||
description: 'A default log view',
|
||||
logIndices: {
|
||||
type: 'index_name',
|
||||
indexName: 'logs-*,filebeat-*',
|
||||
},
|
||||
logColumns: [
|
||||
{
|
||||
timestampColumn: {
|
||||
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: 'eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: {
|
||||
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const defaultLogViewsStaticConfig: LogViewsStaticConfig = {
|
||||
messageFields: defaultSourceConfiguration.fields.message,
|
||||
};
|
|
@ -7,34 +7,34 @@
|
|||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
export class ResolveLogSourceConfigurationError extends Error {
|
||||
export class ResolveLogViewError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'ResolveLogSourceConfigurationError';
|
||||
this.name = 'ResolveLogViewError';
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchLogSourceConfigurationError extends Error {
|
||||
export class FetchLogViewError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'FetchLogSourceConfigurationError';
|
||||
this.name = 'FetchLogViewError';
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchLogSourceStatusError extends Error {
|
||||
export class FetchLogViewStatusError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'FetchLogSourceStatusError';
|
||||
this.name = 'FetchLogViewStatusError';
|
||||
}
|
||||
}
|
||||
|
||||
export class PatchLogSourceConfigurationError extends Error {
|
||||
export class PutLogViewError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'PatchLogSourceConfigurationError';
|
||||
this.name = 'PutLogViewError';
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './defaults';
|
||||
export * from './errors';
|
||||
export * from './log_source_configuration';
|
||||
export * from './resolved_log_source_configuration';
|
||||
export * from './resolved_log_view';
|
||||
export * from './types';
|
26
x-pack/plugins/infra/common/log_views/log_view.mock.ts
Normal file
26
x-pack/plugins/infra/common/log_views/log_view.mock.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { defaultLogViewAttributes } from './defaults';
|
||||
import { LogView, LogViewAttributes, LogViewOrigin } from './types';
|
||||
|
||||
export const createLogViewMock = (
|
||||
id: string,
|
||||
origin: LogViewOrigin = 'stored',
|
||||
attributeOverrides: Partial<LogViewAttributes> = {},
|
||||
updatedAt?: number,
|
||||
version?: string
|
||||
): LogView => ({
|
||||
id,
|
||||
origin,
|
||||
attributes: {
|
||||
...defaultLogViewAttributes,
|
||||
...attributeOverrides,
|
||||
},
|
||||
updatedAt,
|
||||
version,
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataViewsContract, fieldList } from 'src/plugins/data_views/common';
|
||||
import { createStubDataView } from 'src/plugins/data_views/common/stubs';
|
||||
import { defaultLogViewsStaticConfig } from './defaults';
|
||||
import { ResolvedLogView, resolveLogView } from './resolved_log_view';
|
||||
import { LogViewAttributes } from './types';
|
||||
|
||||
export const createResolvedLogViewMock = (
|
||||
resolvedLogViewOverrides: Partial<ResolvedLogView> = {}
|
||||
): ResolvedLogView => ({
|
||||
name: 'LOG VIEW',
|
||||
description: 'LOG VIEW DESCRIPTION',
|
||||
indices: 'log-indices-*',
|
||||
timestampField: 'TIMESTAMP_FIELD',
|
||||
tiebreakerField: 'TIEBREAKER_FIELD',
|
||||
messageField: ['MESSAGE_FIELD'],
|
||||
fields: fieldList(),
|
||||
runtimeMappings: {
|
||||
runtime_field: {
|
||||
type: 'keyword',
|
||||
script: {
|
||||
source: 'emit("runtime value")',
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{ timestampColumn: { id: 'TIMESTAMP_COLUMN_ID' } },
|
||||
{
|
||||
fieldColumn: {
|
||||
id: 'DATASET_COLUMN_ID',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: { id: 'MESSAGE_COLUMN_ID' },
|
||||
},
|
||||
],
|
||||
...resolvedLogViewOverrides,
|
||||
});
|
||||
|
||||
export const createResolvedLogViewMockFromAttributes = (logViewAttributes: LogViewAttributes) =>
|
||||
resolveLogView(
|
||||
logViewAttributes,
|
||||
{
|
||||
get: async () => createStubDataView({ spec: {} }),
|
||||
getFieldsForWildcard: async () => [],
|
||||
} as unknown as DataViewsContract,
|
||||
defaultLogViewsStaticConfig
|
||||
);
|
110
x-pack/plugins/infra/common/log_views/resolved_log_view.ts
Normal file
110
x-pack/plugins/infra/common/log_views/resolved_log_view.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import {
|
||||
DataView,
|
||||
DataViewsContract,
|
||||
FieldSpec,
|
||||
} from '../../../../../src/plugins/data_views/common';
|
||||
import { TIEBREAKER_FIELD, TIMESTAMP_FIELD } from '../constants';
|
||||
import { ResolveLogViewError } from './errors';
|
||||
import { LogViewAttributes, LogViewColumnConfiguration, LogViewsStaticConfig } from './types';
|
||||
|
||||
export type ResolvedLogViewField = FieldSpec;
|
||||
|
||||
export interface ResolvedLogView {
|
||||
name: string;
|
||||
description: string;
|
||||
indices: string;
|
||||
timestampField: string;
|
||||
tiebreakerField: string;
|
||||
messageField: string[];
|
||||
fields: ResolvedLogViewField[];
|
||||
runtimeMappings: estypes.MappingRuntimeFields;
|
||||
columns: LogViewColumnConfiguration[];
|
||||
}
|
||||
|
||||
export const resolveLogView = async (
|
||||
logViewAttributes: LogViewAttributes,
|
||||
dataViewsService: DataViewsContract,
|
||||
config: LogViewsStaticConfig
|
||||
): Promise<ResolvedLogView> => {
|
||||
if (logViewAttributes.logIndices.type === 'index_name') {
|
||||
return await resolveLegacyReference(logViewAttributes, dataViewsService, config);
|
||||
} else {
|
||||
return await resolveDataViewReference(logViewAttributes, dataViewsService);
|
||||
}
|
||||
};
|
||||
|
||||
const resolveLegacyReference = async (
|
||||
logViewAttributes: LogViewAttributes,
|
||||
dataViewsService: DataViewsContract,
|
||||
config: LogViewsStaticConfig
|
||||
): Promise<ResolvedLogView> => {
|
||||
if (logViewAttributes.logIndices.type !== 'index_name') {
|
||||
throw new Error('This function can only resolve legacy references');
|
||||
}
|
||||
|
||||
const indices = logViewAttributes.logIndices.indexName;
|
||||
|
||||
const fields = await dataViewsService
|
||||
.getFieldsForWildcard({
|
||||
pattern: indices,
|
||||
allowNoIndex: true,
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new ResolveLogViewError(
|
||||
`Failed to fetch fields for indices "${indices}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: logViewAttributes.logIndices.indexName,
|
||||
timestampField: TIMESTAMP_FIELD,
|
||||
tiebreakerField: TIEBREAKER_FIELD,
|
||||
messageField: config.messageFields,
|
||||
fields,
|
||||
runtimeMappings: {},
|
||||
columns: logViewAttributes.logColumns,
|
||||
name: logViewAttributes.name,
|
||||
description: logViewAttributes.description,
|
||||
};
|
||||
};
|
||||
|
||||
const resolveDataViewReference = async (
|
||||
logViewAttributes: LogViewAttributes,
|
||||
dataViewsService: DataViewsContract
|
||||
): Promise<ResolvedLogView> => {
|
||||
if (logViewAttributes.logIndices.type !== 'data_view') {
|
||||
throw new Error('This function can only resolve Kibana data view references');
|
||||
}
|
||||
|
||||
const { dataViewId } = logViewAttributes.logIndices;
|
||||
|
||||
const dataView = await dataViewsService.get(dataViewId).catch((error) => {
|
||||
throw new ResolveLogViewError(`Failed to fetch data view "${dataViewId}": ${error}`, error);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: dataView.title,
|
||||
timestampField: dataView.timeFieldName ?? TIMESTAMP_FIELD,
|
||||
tiebreakerField: TIEBREAKER_FIELD,
|
||||
messageField: ['message'],
|
||||
fields: dataView.fields,
|
||||
runtimeMappings: resolveRuntimeMappings(dataView),
|
||||
columns: logViewAttributes.logColumns,
|
||||
name: logViewAttributes.name,
|
||||
description: logViewAttributes.description,
|
||||
};
|
||||
};
|
||||
|
||||
// this might take other sources of runtime fields into account in the future
|
||||
const resolveRuntimeMappings = (dataView: DataView): estypes.MappingRuntimeFields => {
|
||||
return dataView.getRuntimeMappings();
|
||||
};
|
103
x-pack/plugins/infra/common/log_views/types.ts
Normal file
103
x-pack/plugins/infra/common/log_views/types.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export interface LogViewsStaticConfig {
|
||||
messageFields: string[];
|
||||
}
|
||||
|
||||
export const logViewOriginRT = rt.keyof({
|
||||
stored: null,
|
||||
internal: null,
|
||||
'infra-source-stored': null,
|
||||
'infra-source-internal': null,
|
||||
'infra-source-fallback': null,
|
||||
});
|
||||
export type LogViewOrigin = rt.TypeOf<typeof logViewOriginRT>;
|
||||
|
||||
// Kibana data views
|
||||
export const logDataViewReferenceRT = rt.type({
|
||||
type: rt.literal('data_view'),
|
||||
dataViewId: rt.string,
|
||||
});
|
||||
|
||||
export type LogDataViewReference = rt.TypeOf<typeof logDataViewReferenceRT>;
|
||||
|
||||
// Index name
|
||||
export const logIndexNameReferenceRT = rt.type({
|
||||
type: rt.literal('index_name'),
|
||||
indexName: rt.string,
|
||||
});
|
||||
export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>;
|
||||
|
||||
export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]);
|
||||
export type LogIndexReference = rt.TypeOf<typeof logIndexReferenceRT>;
|
||||
|
||||
const logViewCommonColumnConfigurationRT = rt.strict({
|
||||
id: rt.string,
|
||||
});
|
||||
|
||||
const logViewTimestampColumnConfigurationRT = rt.strict({
|
||||
timestampColumn: logViewCommonColumnConfigurationRT,
|
||||
});
|
||||
|
||||
const logViewMessageColumnConfigurationRT = rt.strict({
|
||||
messageColumn: logViewCommonColumnConfigurationRT,
|
||||
});
|
||||
|
||||
export const logViewFieldColumnConfigurationRT = rt.strict({
|
||||
fieldColumn: rt.intersection([
|
||||
logViewCommonColumnConfigurationRT,
|
||||
rt.strict({
|
||||
field: rt.string,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const logViewColumnConfigurationRT = rt.union([
|
||||
logViewTimestampColumnConfigurationRT,
|
||||
logViewMessageColumnConfigurationRT,
|
||||
logViewFieldColumnConfigurationRT,
|
||||
]);
|
||||
export type LogViewColumnConfiguration = rt.TypeOf<typeof logViewColumnConfigurationRT>;
|
||||
|
||||
export const logViewAttributesRT = rt.strict({
|
||||
name: rt.string,
|
||||
description: rt.string,
|
||||
logIndices: logIndexReferenceRT,
|
||||
logColumns: rt.array(logViewColumnConfigurationRT),
|
||||
});
|
||||
export type LogViewAttributes = rt.TypeOf<typeof logViewAttributesRT>;
|
||||
|
||||
export const logViewRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
origin: logViewOriginRT,
|
||||
attributes: logViewAttributesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
export type LogView = rt.TypeOf<typeof logViewRT>;
|
||||
|
||||
export const logViewIndexStatusRT = rt.keyof({
|
||||
available: null,
|
||||
empty: null,
|
||||
missing: null,
|
||||
unknown: null,
|
||||
});
|
||||
export type LogViewIndexStatus = rt.TypeOf<typeof logViewIndexStatusRT>;
|
||||
|
||||
export const logViewStatusRT = rt.strict({
|
||||
index: logViewIndexStatusRT,
|
||||
});
|
||||
export type LogViewStatus = rt.TypeOf<typeof logViewStatusRT>;
|
37
x-pack/plugins/infra/common/plugin_config_types.ts
Normal file
37
x-pack/plugins/infra/common/plugin_config_types.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface InfraConfig {
|
||||
alerting: {
|
||||
inventory_threshold: {
|
||||
group_by_page_size: number;
|
||||
};
|
||||
metric_threshold: {
|
||||
group_by_page_size: number;
|
||||
};
|
||||
};
|
||||
inventory: {
|
||||
compositeSize: number;
|
||||
};
|
||||
sources?: {
|
||||
default?: {
|
||||
fields?: {
|
||||
message?: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const publicConfigKeys = {
|
||||
sources: true,
|
||||
} as const;
|
||||
|
||||
export type InfraPublicConfigKey = keyof {
|
||||
[K in keyof typeof publicConfigKeys as typeof publicConfigKeys[K] extends true ? K : never]: true;
|
||||
};
|
||||
|
||||
export type InfraPublicConfig = Pick<InfraConfig, InfraPublicConfigKey>;
|
|
@ -5,15 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { logSourceColumnConfigurationRT } from '../../log_sources/log_source_configuration';
|
||||
import * as rt from 'io-ts';
|
||||
import {
|
||||
logEntryAfterCursorRT,
|
||||
logEntryBeforeCursorRT,
|
||||
logEntryCursorRT,
|
||||
logEntryRT,
|
||||
} from '../../log_entry';
|
||||
import { logViewColumnConfigurationRT } from '../../log_views';
|
||||
import { jsonObjectRT } from '../../typed_json';
|
||||
import { searchStrategyErrorRT } from '../common/errors';
|
||||
|
||||
|
@ -28,7 +28,7 @@ const logEntriesBaseSearchRequestParamsRT = rt.intersection([
|
|||
}),
|
||||
rt.partial({
|
||||
query: jsonObjectRT,
|
||||
columns: rt.array(logSourceColumnConfigurationRT),
|
||||
columns: rt.array(logViewColumnConfigurationRT),
|
||||
highlightPhrase: rt.string,
|
||||
}),
|
||||
]);
|
||||
|
|
43
x-pack/plugins/infra/common/source_configuration/defaults.ts
Normal file
43
x-pack/plugins/infra/common/source_configuration/defaults.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LOGS_INDEX_PATTERN, METRICS_INDEX_PATTERN } from '../constants';
|
||||
import { InfraSourceConfiguration } from './source_configuration';
|
||||
|
||||
export const defaultSourceConfiguration: InfraSourceConfiguration = {
|
||||
name: 'Default',
|
||||
description: '',
|
||||
metricAlias: METRICS_INDEX_PATTERN,
|
||||
logIndices: {
|
||||
type: 'index_name',
|
||||
indexName: LOGS_INDEX_PATTERN,
|
||||
},
|
||||
fields: {
|
||||
message: ['message', '@message'],
|
||||
},
|
||||
inventoryDefaultView: '0',
|
||||
metricsExplorerDefaultView: '0',
|
||||
logColumns: [
|
||||
{
|
||||
timestampColumn: {
|
||||
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: ' eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: {
|
||||
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
|
||||
},
|
||||
},
|
||||
],
|
||||
anomalyThreshold: 50,
|
||||
};
|
|
@ -22,7 +22,6 @@ import * as rt from 'io-ts';
|
|||
import moment from 'moment';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { chain } from 'fp-ts/lib/Either';
|
||||
import { logIndexReferenceRT } from '../log_sources';
|
||||
|
||||
export const TimestampFromString = new rt.Type<number, string>(
|
||||
'TimestampFromString',
|
||||
|
@ -103,6 +102,27 @@ export const SourceConfigurationColumnRuntimeType = rt.union([
|
|||
|
||||
export type InfraSourceConfigurationColumn = rt.TypeOf<typeof SourceConfigurationColumnRuntimeType>;
|
||||
|
||||
/**
|
||||
* Log indices
|
||||
*/
|
||||
|
||||
// Kibana index pattern
|
||||
export const logIndexPatternReferenceRT = rt.type({
|
||||
type: rt.literal('index_pattern'),
|
||||
indexPatternId: rt.string,
|
||||
});
|
||||
export type LogIndexPatternReference = rt.TypeOf<typeof logIndexPatternReferenceRT>;
|
||||
|
||||
// Legacy support
|
||||
export const logIndexNameReferenceRT = rt.type({
|
||||
type: rt.literal('index_name'),
|
||||
indexName: rt.string,
|
||||
});
|
||||
export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>;
|
||||
|
||||
export const logIndexReferenceRT = rt.union([logIndexPatternReferenceRT, logIndexNameReferenceRT]);
|
||||
export type LogIndexReference = rt.TypeOf<typeof logIndexReferenceRT>;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
|
|
@ -48,3 +48,5 @@ export type ObjectValues<T> = Array<T[keyof T]>;
|
|||
|
||||
export type ObjectEntry<T> = [keyof T, T[keyof T]];
|
||||
export type ObjectEntries<T> = Array<ObjectEntry<T>>;
|
||||
|
||||
export type UnwrapPromise<T extends Promise<any>> = T extends Promise<infer Value> ? Value : never;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"embeddable",
|
||||
"data",
|
||||
"dataEnhanced",
|
||||
"dataViews",
|
||||
"visTypeTimeseries",
|
||||
"alerting",
|
||||
"triggersActionsUi",
|
||||
|
|
|
@ -5,21 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { DataViewField } from 'src/plugins/data_views/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiPopoverTitle,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiPopover,
|
||||
EuiExpression,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { FieldSpec } from 'src/plugins/data_views/common';
|
||||
import { GroupBySelector } from './selector';
|
||||
|
||||
interface Props {
|
||||
selectedGroups?: string[];
|
||||
fields: DataViewField[];
|
||||
fields: FieldSpec[];
|
||||
onChange: (groupBy: string[]) => void;
|
||||
label?: string;
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { DataViewField } from 'src/plugins/data_views/common';
|
||||
import { FieldSpec } from 'src/plugins/data_views/common';
|
||||
|
||||
interface Props {
|
||||
selectedGroups?: string[];
|
||||
onChange: (groupBy: string[]) => void;
|
||||
fields: DataViewField[];
|
||||
fields: FieldSpec[];
|
||||
label: string;
|
||||
placeholder: string;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useCallback } from 'react';
|
|||
import { EuiFlexItem, EuiFlexGroup, EuiButtonEmpty, EuiAccordion, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataViewField } from 'src/plugins/data_views/common';
|
||||
import type { ResolvedLogViewField } from '../../../../../common/log_views';
|
||||
import { Criterion } from './criterion';
|
||||
import {
|
||||
PartialRuleParams,
|
||||
|
@ -34,7 +34,7 @@ const QueryBText = i18n.translate('xpack.infra.logs.alerting.threshold.ratioCrit
|
|||
});
|
||||
|
||||
interface SharedProps {
|
||||
fields: DataViewField[];
|
||||
fields: ResolvedLogViewField[];
|
||||
criteria?: PartialCriteriaType;
|
||||
defaultCriterion: PartialCriterionType;
|
||||
errors: Errors['criteria'];
|
||||
|
|
|
@ -5,30 +5,29 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
EuiPopoverTitle,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiPopover,
|
||||
EuiSelect,
|
||||
EuiFieldNumber,
|
||||
EuiExpression,
|
||||
EuiFieldText,
|
||||
EuiButtonIcon,
|
||||
EuiFormRow,
|
||||
EuiComboBox,
|
||||
EuiExpression,
|
||||
EuiFieldNumber,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiSelect,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataViewField } from 'src/plugins/data_views/common';
|
||||
import { isNumber, isFinite } from 'lodash';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types';
|
||||
import { isFinite, isNumber } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { IErrorObject } from '../../../../../../triggers_actions_ui/public';
|
||||
import {
|
||||
Comparator,
|
||||
Criterion as CriterionType,
|
||||
ComparatorToi18nMap,
|
||||
Criterion as CriterionType,
|
||||
} from '../../../../../common/alerting/logs/log_threshold/types';
|
||||
import type { ResolvedLogViewField } from '../../../../../common/log_views';
|
||||
|
||||
const firstCriterionFieldPrefix = i18n.translate(
|
||||
'xpack.infra.logs.alertFlyout.firstCriterionFieldPrefix',
|
||||
|
@ -55,7 +54,7 @@ const criterionComparatorValueTitle = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const getCompatibleComparatorsForField = (fieldInfo: DataViewField | undefined) => {
|
||||
const getCompatibleComparatorsForField = (fieldInfo: ResolvedLogViewField | undefined) => {
|
||||
if (fieldInfo?.type === 'number') {
|
||||
return [
|
||||
{ value: Comparator.GT, text: ComparatorToi18nMap[Comparator.GT] },
|
||||
|
@ -83,7 +82,10 @@ const getCompatibleComparatorsForField = (fieldInfo: DataViewField | undefined)
|
|||
}
|
||||
};
|
||||
|
||||
const getFieldInfo = (fields: DataViewField[], fieldName: string): DataViewField | undefined => {
|
||||
const getFieldInfo = (
|
||||
fields: ResolvedLogViewField[],
|
||||
fieldName: string
|
||||
): ResolvedLogViewField | undefined => {
|
||||
return fields.find((field) => {
|
||||
return field.name === fieldName;
|
||||
});
|
||||
|
@ -91,7 +93,7 @@ const getFieldInfo = (fields: DataViewField[], fieldName: string): DataViewField
|
|||
|
||||
interface Props {
|
||||
idx: number;
|
||||
fields: DataViewField[];
|
||||
fields: ResolvedLogViewField[];
|
||||
criterion: Partial<CriterionType>;
|
||||
updateCriterion: (idx: number, params: Partial<CriterionType>) => void;
|
||||
removeCriterion: (idx: number) => void;
|
||||
|
@ -117,7 +119,7 @@ export const Criterion: React.FC<Props> = ({
|
|||
});
|
||||
}, [fields]);
|
||||
|
||||
const fieldInfo: DataViewField | undefined = useMemo(() => {
|
||||
const fieldInfo: ResolvedLogViewField | undefined = useMemo(() => {
|
||||
if (criterion.field) {
|
||||
return getFieldInfo(fields, criterion.field);
|
||||
} else {
|
||||
|
|
|
@ -9,30 +9,27 @@ import { EuiButton, EuiCallOut, EuiLoadingSpinner, EuiSpacer } from '@elastic/eu
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ResolvedLogViewField } from '../../../../../common/log_views';
|
||||
import {
|
||||
RuleTypeParamsExpressionProps,
|
||||
ForLastExpression,
|
||||
RuleTypeParamsExpressionProps,
|
||||
} from '../../../../../../triggers_actions_ui/public';
|
||||
import {
|
||||
Comparator,
|
||||
isOptimizableGroupedThreshold,
|
||||
isRatioRule,
|
||||
PartialRuleParams,
|
||||
PartialCountRuleParams,
|
||||
PartialCriteria as PartialCriteriaType,
|
||||
PartialRatioRuleParams,
|
||||
PartialRuleParams,
|
||||
ThresholdType,
|
||||
timeUnitRT,
|
||||
isOptimizableGroupedThreshold,
|
||||
} from '../../../../../common/alerting/logs/log_threshold/types';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
import { ObjectEntries } from '../../../../../common/utility_types';
|
||||
import {
|
||||
LogIndexField,
|
||||
LogSourceProvider,
|
||||
useLogSourceContext,
|
||||
} from '../../../../containers/logs/log_source';
|
||||
import { useSourceId } from '../../../../containers/source_id';
|
||||
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
|
||||
import { LogViewProvider, useLogViewContext } from '../../../../hooks/use_log_view';
|
||||
import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression';
|
||||
import { errorsRT } from '../../validation';
|
||||
import { Criteria } from './criteria';
|
||||
|
@ -57,7 +54,7 @@ const DEFAULT_BASE_EXPRESSION = {
|
|||
const DEFAULT_FIELD = 'log.level';
|
||||
|
||||
const createDefaultCriterion = (
|
||||
availableFields: LogIndexField[],
|
||||
availableFields: ResolvedLogViewField[],
|
||||
value: ExpressionCriteria['value']
|
||||
) =>
|
||||
availableFields.some((availableField) => availableField.name === DEFAULT_FIELD)
|
||||
|
@ -65,7 +62,7 @@ const createDefaultCriterion = (
|
|||
: { field: undefined, comparator: undefined, value: undefined };
|
||||
|
||||
const createDefaultCountRuleParams = (
|
||||
availableFields: LogIndexField[]
|
||||
availableFields: ResolvedLogViewField[]
|
||||
): PartialCountRuleParams => ({
|
||||
...DEFAULT_BASE_EXPRESSION,
|
||||
count: {
|
||||
|
@ -76,7 +73,7 @@ const createDefaultCountRuleParams = (
|
|||
});
|
||||
|
||||
const createDefaultRatioRuleParams = (
|
||||
availableFields: LogIndexField[]
|
||||
availableFields: ResolvedLogViewField[]
|
||||
): PartialRatioRuleParams => ({
|
||||
...DEFAULT_BASE_EXPRESSION,
|
||||
count: {
|
||||
|
@ -93,8 +90,10 @@ export const ExpressionEditor: React.FC<
|
|||
RuleTypeParamsExpressionProps<PartialRuleParams, LogsContextMeta>
|
||||
> = (props) => {
|
||||
const isInternal = props.metadata?.isInternal ?? false;
|
||||
const [sourceId] = useSourceId();
|
||||
const { http } = useKibana().services;
|
||||
const [logViewId] = useSourceId();
|
||||
const {
|
||||
services: { http, logViews },
|
||||
} = useKibanaContextForPlugin(); // injected during alert registration
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -103,42 +102,28 @@ export const ExpressionEditor: React.FC<
|
|||
<Editor {...props} />
|
||||
</SourceStatusWrapper>
|
||||
) : (
|
||||
<LogSourceProvider
|
||||
sourceId={sourceId}
|
||||
fetch={http!.fetch}
|
||||
indexPatternsService={props.data.indexPatterns}
|
||||
>
|
||||
<LogViewProvider logViewId={logViewId} logViews={logViews.client} fetch={http.fetch}>
|
||||
<SourceStatusWrapper {...props}>
|
||||
<Editor {...props} />
|
||||
</SourceStatusWrapper>
|
||||
</LogSourceProvider>
|
||||
</LogViewProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SourceStatusWrapper: React.FC = ({ children }) => {
|
||||
const {
|
||||
initialize,
|
||||
loadSource,
|
||||
isLoadingSourceConfiguration,
|
||||
hasFailedLoadingSource,
|
||||
isUninitialized,
|
||||
} = useLogSourceContext();
|
||||
|
||||
useMount(() => {
|
||||
initialize();
|
||||
});
|
||||
const { load, isLoading, hasFailedLoading, isUninitialized } = useLogViewContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoadingSourceConfiguration || isUninitialized ? (
|
||||
{isLoading || isUninitialized ? (
|
||||
<div>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLoadingSpinner size="l" />
|
||||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
) : hasFailedLoadingSource ? (
|
||||
) : hasFailedLoading ? (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.infra.logs.alertFlyout.sourceStatusError', {
|
||||
defaultMessage: 'Sorry, there was a problem loading field information',
|
||||
|
@ -146,7 +131,7 @@ export const SourceStatusWrapper: React.FC = ({ children }) => {
|
|||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<EuiButton onClick={loadSource} iconType="refresh">
|
||||
<EuiButton onClick={load} iconType="refresh">
|
||||
{i18n.translate('xpack.infra.logs.alertFlyout.sourceStatusErrorTryAgain', {
|
||||
defaultMessage: 'Try again',
|
||||
})}
|
||||
|
@ -164,7 +149,7 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
) => {
|
||||
const { setRuleParams, ruleParams, errors } = props;
|
||||
const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false);
|
||||
const { sourceId, resolvedSourceConfiguration } = useLogSourceContext();
|
||||
const { logViewId, resolvedLogView } = useLogViewContext();
|
||||
|
||||
const {
|
||||
criteria: criteriaErrors,
|
||||
|
@ -174,24 +159,24 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
} = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]);
|
||||
|
||||
const supportedFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
if (resolvedLogView?.fields) {
|
||||
return resolvedLogView.fields.filter((field) => {
|
||||
return (field.type === 'string' || field.type === 'number') && field.searchable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
}, [resolvedLogView]);
|
||||
|
||||
const groupByFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
if (resolvedLogView?.fields) {
|
||||
return resolvedLogView.fields.filter((field) => {
|
||||
return field.type === 'string' && field.aggregatable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
}, [resolvedLogView]);
|
||||
|
||||
const updateThreshold = useCallback(
|
||||
(thresholdParams) => {
|
||||
|
@ -276,7 +261,7 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
defaultCriterion={defaultCountAlertParams.criteria[0]}
|
||||
errors={criteriaErrors}
|
||||
ruleParams={ruleParams}
|
||||
sourceId={sourceId}
|
||||
sourceId={logViewId}
|
||||
updateCriteria={updateCriteria}
|
||||
/>
|
||||
) : null;
|
||||
|
|
|
@ -6,16 +6,24 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
|
||||
import {
|
||||
LOG_DOCUMENT_COUNT_RULE_TYPE_ID,
|
||||
PartialRuleParams,
|
||||
} from '../../../common/alerting/logs/log_threshold';
|
||||
import { createLazyComponentWithKibanaContext } from '../../hooks/use_kibana';
|
||||
import { InfraClientCoreSetup } from '../../types';
|
||||
import { formatRuleData } from './rule_data_formatters';
|
||||
import { validateExpression } from './validation';
|
||||
|
||||
export function createLogThresholdRuleType(): ObservabilityRuleTypeModel<PartialRuleParams> {
|
||||
export function createLogThresholdRuleType(
|
||||
core: InfraClientCoreSetup
|
||||
): ObservabilityRuleTypeModel<PartialRuleParams> {
|
||||
const ruleParamsExpression = createLazyComponentWithKibanaContext(
|
||||
core,
|
||||
() => import('./components/expression_editor/editor')
|
||||
);
|
||||
|
||||
return {
|
||||
id: LOG_DOCUMENT_COUNT_RULE_TYPE_ID,
|
||||
description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', {
|
||||
|
@ -25,7 +33,7 @@ export function createLogThresholdRuleType(): ObservabilityRuleTypeModel<Partial
|
|||
documentationUrl(docLinks) {
|
||||
return `${docLinks.links.observability.logsThreshold}`;
|
||||
},
|
||||
ruleParamsExpression: React.lazy(() => import('./components/expression_editor/editor')),
|
||||
ruleParamsExpression,
|
||||
validate: validateExpression,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.infra.logs.alerting.threshold.defaultActionMessage',
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
|
@ -14,11 +14,11 @@ import {
|
|||
useUiSetting$,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
|
||||
import { createKibanaContextForPlugin } from '../hooks/use_kibana';
|
||||
import { InfraClientStartDeps } from '../types';
|
||||
import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider';
|
||||
import { NavigationWarningPromptProvider } from '../../../observability/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
|
||||
import { useKibanaContextForPluginProvider } from '../hooks/use_kibana';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider';
|
||||
import { TriggersActionsProvider } from '../utils/triggers_actions_context';
|
||||
|
||||
export const CommonInfraProviders: React.FC<{
|
||||
|
@ -45,6 +45,7 @@ export const CommonInfraProviders: React.FC<{
|
|||
|
||||
export interface CoreProvidersProps {
|
||||
core: CoreStart;
|
||||
pluginStart: InfraClientStartExports;
|
||||
plugins: InfraClientStartDeps;
|
||||
theme$: AppMountParameters['theme$'];
|
||||
}
|
||||
|
@ -52,16 +53,18 @@ export interface CoreProvidersProps {
|
|||
export const CoreProviders: React.FC<CoreProvidersProps> = ({
|
||||
children,
|
||||
core,
|
||||
pluginStart,
|
||||
plugins,
|
||||
theme$,
|
||||
}) => {
|
||||
const { Provider: KibanaContextProviderForPlugin } = useMemo(
|
||||
() => createKibanaContextForPlugin(core, plugins),
|
||||
[core, plugins]
|
||||
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(
|
||||
core,
|
||||
plugins,
|
||||
pluginStart
|
||||
);
|
||||
|
||||
return (
|
||||
<KibanaContextProviderForPlugin services={{ ...core, ...plugins }}>
|
||||
<KibanaContextProviderForPlugin services={{ ...core, ...plugins, ...pluginStart }}>
|
||||
<core.i18n.Context>
|
||||
<KibanaThemeProvider theme$={theme$}>{children}</KibanaThemeProvider>
|
||||
</core.i18n.Context>
|
||||
|
|
|
@ -16,13 +16,14 @@ import '../index.scss';
|
|||
import { NotFoundPage } from '../pages/404';
|
||||
import { LinkToLogsPage } from '../pages/link_to/link_to_logs';
|
||||
import { LogsPage } from '../pages/logs';
|
||||
import { InfraClientStartDeps } from '../types';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
import { CommonInfraProviders, CoreProviders } from './common_providers';
|
||||
import { prepareMountElement } from './common_styles';
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
plugins: InfraClientStartDeps,
|
||||
pluginStart: InfraClientStartExports,
|
||||
{ element, history, setHeaderActionMenu, theme$ }: AppMountParameters
|
||||
) => {
|
||||
const storage = new Storage(window.localStorage);
|
||||
|
@ -35,6 +36,7 @@ export const renderApp = (
|
|||
storage={storage}
|
||||
history={history}
|
||||
plugins={plugins}
|
||||
pluginStart={pluginStart}
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
theme$={theme$}
|
||||
/>,
|
||||
|
@ -49,15 +51,16 @@ export const renderApp = (
|
|||
const LogsApp: React.FC<{
|
||||
core: CoreStart;
|
||||
history: History<unknown>;
|
||||
pluginStart: InfraClientStartExports;
|
||||
plugins: InfraClientStartDeps;
|
||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
storage: Storage;
|
||||
theme$: AppMountParameters['theme$'];
|
||||
}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => {
|
||||
}> = ({ core, history, pluginStart, plugins, setHeaderActionMenu, storage, theme$ }) => {
|
||||
const uiCapabilities = core.application.capabilities;
|
||||
|
||||
return (
|
||||
<CoreProviders core={core} plugins={plugins} theme$={theme$}>
|
||||
<CoreProviders core={core} pluginStart={pluginStart} plugins={plugins} theme$={theme$}>
|
||||
<CommonInfraProviders
|
||||
appName="Logs UI"
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
|
|
|
@ -16,7 +16,7 @@ import '../index.scss';
|
|||
import { NotFoundPage } from '../pages/404';
|
||||
import { LinkToMetricsPage } from '../pages/link_to/link_to_metrics';
|
||||
import { InfrastructurePage } from '../pages/metrics';
|
||||
import { InfraClientStartDeps } from '../types';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
import { RedirectWithQueryParams } from '../utils/redirect_with_query_params';
|
||||
import { CommonInfraProviders, CoreProviders } from './common_providers';
|
||||
import { prepareMountElement } from './common_styles';
|
||||
|
@ -24,6 +24,7 @@ import { prepareMountElement } from './common_styles';
|
|||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
plugins: InfraClientStartDeps,
|
||||
pluginStart: InfraClientStartExports,
|
||||
{ element, history, setHeaderActionMenu, theme$ }: AppMountParameters
|
||||
) => {
|
||||
const storage = new Storage(window.localStorage);
|
||||
|
@ -35,6 +36,7 @@ export const renderApp = (
|
|||
core={core}
|
||||
history={history}
|
||||
plugins={plugins}
|
||||
pluginStart={pluginStart}
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
storage={storage}
|
||||
theme$={theme$}
|
||||
|
@ -50,15 +52,16 @@ export const renderApp = (
|
|||
const MetricsApp: React.FC<{
|
||||
core: CoreStart;
|
||||
history: History<unknown>;
|
||||
pluginStart: InfraClientStartExports;
|
||||
plugins: InfraClientStartDeps;
|
||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
storage: Storage;
|
||||
theme$: AppMountParameters['theme$'];
|
||||
}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => {
|
||||
}> = ({ core, history, pluginStart, plugins, setHeaderActionMenu, storage, theme$ }) => {
|
||||
const uiCapabilities = core.application.capabilities;
|
||||
|
||||
return (
|
||||
<CoreProviders core={core} plugins={plugins} theme$={theme$}>
|
||||
<CoreProviders core={core} pluginStart={pluginStart} plugins={plugins} theme$={theme$}>
|
||||
<CommonInfraProviders
|
||||
appName="Metrics UI"
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
NodeMetricsTableFetchMock,
|
||||
SourceResponseMock,
|
||||
} from '../test_helpers';
|
||||
import { createCoreProvidersPropsMock } from '../test_helpers';
|
||||
import { createStartServicesAccessorMock } from '../test_helpers';
|
||||
import { createLazyContainerMetricsTable } from './create_lazy_container_metrics_table';
|
||||
import IntegratedContainerMetricsTable from './integrated_container_metrics_table';
|
||||
import { metricByField } from './use_container_metrics_table';
|
||||
|
@ -41,8 +41,8 @@ describe('ContainerMetricsTable', () => {
|
|||
|
||||
describe('createLazyContainerMetricsTable', () => {
|
||||
it('should lazily load and render the table', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const LazyContainerMetricsTable = createLazyContainerMetricsTable(coreProvidersPropsMock);
|
||||
const { fetch, getStartServices } = createStartServicesAccessorMock(fetchMock);
|
||||
const LazyContainerMetricsTable = createLazyContainerMetricsTable(getStartServices);
|
||||
|
||||
render(<LazyContainerMetricsTable timerange={timerange} filterClauseDsl={filterClauseDsl} />);
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('ContainerMetricsTable', () => {
|
|||
|
||||
describe('IntegratedContainerMetricsTable', () => {
|
||||
it('should render a single row of data', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const { coreProvidersPropsMock, fetch } = createStartServicesAccessorMock(fetchMock);
|
||||
|
||||
const { findByText } = render(
|
||||
<IntegratedContainerMetricsTable
|
||||
|
|
|
@ -6,26 +6,33 @@
|
|||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import type { CoreProvidersProps } from '../../../apps/common_providers';
|
||||
import { InfraClientStartServices } from '../../../types';
|
||||
import type { SourceProviderProps, UseNodeMetricsTableOptions } from '../shared';
|
||||
|
||||
const LazyIntegratedContainerMetricsTable = lazy(
|
||||
() => import('./integrated_container_metrics_table')
|
||||
);
|
||||
|
||||
export function createLazyContainerMetricsTable(coreProvidersProps: CoreProvidersProps) {
|
||||
export function createLazyContainerMetricsTable(getStartServices: () => InfraClientStartServices) {
|
||||
return ({
|
||||
timerange,
|
||||
filterClauseDsl,
|
||||
sourceId,
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedContainerMetricsTable
|
||||
{...coreProvidersProps}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => {
|
||||
const [core, plugins, pluginStart] = getStartServices();
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedContainerMetricsTable
|
||||
core={core}
|
||||
plugins={plugins}
|
||||
pluginStart={pluginStart}
|
||||
theme$={core.theme.theme$}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,24 +6,31 @@
|
|||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import type { CoreProvidersProps } from '../../../apps/common_providers';
|
||||
import { InfraClientStartServices } from '../../../types';
|
||||
import type { SourceProviderProps, UseNodeMetricsTableOptions } from '../shared';
|
||||
|
||||
const LazyIntegratedHostMetricsTable = lazy(() => import('./integrated_host_metrics_table'));
|
||||
|
||||
export function createLazyHostMetricsTable(coreProvidersProps: CoreProvidersProps) {
|
||||
export function createLazyHostMetricsTable(getStartServices: () => InfraClientStartServices) {
|
||||
return ({
|
||||
timerange,
|
||||
filterClauseDsl,
|
||||
sourceId,
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedHostMetricsTable
|
||||
{...coreProvidersProps}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => {
|
||||
const [core, plugins, pluginStart] = getStartServices();
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedHostMetricsTable
|
||||
core={core}
|
||||
plugins={plugins}
|
||||
pluginStart={pluginStart}
|
||||
theme$={core.theme.theme$}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
NodeMetricsTableFetchMock,
|
||||
SourceResponseMock,
|
||||
} from '../test_helpers';
|
||||
import { createCoreProvidersPropsMock } from '../test_helpers';
|
||||
import { createStartServicesAccessorMock } from '../test_helpers';
|
||||
import { createLazyHostMetricsTable } from './create_lazy_host_metrics_table';
|
||||
import IntegratedHostMetricsTable from './integrated_host_metrics_table';
|
||||
import { metricByField } from './use_host_metrics_table';
|
||||
|
@ -41,8 +41,8 @@ describe('HostMetricsTable', () => {
|
|||
|
||||
describe('createLazyHostMetricsTable', () => {
|
||||
it('should lazily load and render the table', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const LazyHostMetricsTable = createLazyHostMetricsTable(coreProvidersPropsMock);
|
||||
const { fetch, getStartServices } = createStartServicesAccessorMock(fetchMock);
|
||||
const LazyHostMetricsTable = createLazyHostMetricsTable(getStartServices);
|
||||
|
||||
render(<LazyHostMetricsTable timerange={timerange} filterClauseDsl={filterClauseDsl} />);
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('HostMetricsTable', () => {
|
|||
|
||||
describe('IntegratedHostMetricsTable', () => {
|
||||
it('should render a single row of data', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const { coreProvidersPropsMock, fetch } = createStartServicesAccessorMock(fetchMock);
|
||||
|
||||
const { findByText } = render(
|
||||
<IntegratedHostMetricsTable
|
||||
|
|
|
@ -6,24 +6,31 @@
|
|||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import type { CoreProvidersProps } from '../../../apps/common_providers';
|
||||
import { InfraClientStartServices } from '../../../types';
|
||||
import type { SourceProviderProps, UseNodeMetricsTableOptions } from '../shared';
|
||||
|
||||
const LazyIntegratedPodMetricsTable = lazy(() => import('./integrated_pod_metrics_table'));
|
||||
|
||||
export function createLazyPodMetricsTable(coreProvidersProps: CoreProvidersProps) {
|
||||
export function createLazyPodMetricsTable(getStartServices: () => InfraClientStartServices) {
|
||||
return ({
|
||||
timerange,
|
||||
filterClauseDsl,
|
||||
sourceId,
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedPodMetricsTable
|
||||
{...coreProvidersProps}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}: UseNodeMetricsTableOptions & Partial<SourceProviderProps>) => {
|
||||
const [core, plugins, pluginStart] = getStartServices();
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<LazyIntegratedPodMetricsTable
|
||||
core={core}
|
||||
plugins={plugins}
|
||||
pluginStart={pluginStart}
|
||||
theme$={core.theme.theme$}
|
||||
sourceId={sourceId || 'default'}
|
||||
timerange={timerange}
|
||||
filterClauseDsl={filterClauseDsl}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
NodeMetricsTableFetchMock,
|
||||
SourceResponseMock,
|
||||
} from '../test_helpers';
|
||||
import { createCoreProvidersPropsMock } from '../test_helpers';
|
||||
import { createStartServicesAccessorMock } from '../test_helpers';
|
||||
import { createLazyPodMetricsTable } from './create_lazy_pod_metrics_table';
|
||||
import IntegratedPodMetricsTable from './integrated_pod_metrics_table';
|
||||
import { metricByField } from './use_pod_metrics_table';
|
||||
|
@ -41,8 +41,8 @@ describe('PodMetricsTable', () => {
|
|||
|
||||
describe('createLazyPodMetricsTable', () => {
|
||||
it('should lazily load and render the table', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const LazyPodMetricsTable = createLazyPodMetricsTable(coreProvidersPropsMock);
|
||||
const { fetch, getStartServices } = createStartServicesAccessorMock(fetchMock);
|
||||
const LazyPodMetricsTable = createLazyPodMetricsTable(getStartServices);
|
||||
|
||||
render(<LazyPodMetricsTable timerange={timerange} filterClauseDsl={filterClauseDsl} />);
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('PodMetricsTable', () => {
|
|||
|
||||
describe('IntegratedPodMetricsTable', () => {
|
||||
it('should render a single row of data', async () => {
|
||||
const { coreProvidersPropsMock, fetch } = createCoreProvidersPropsMock(fetchMock);
|
||||
const { coreProvidersPropsMock, fetch } = createStartServicesAccessorMock(fetchMock);
|
||||
|
||||
const { findByText } = render(
|
||||
<IntegratedPodMetricsTable
|
||||
|
|
|
@ -11,7 +11,11 @@ import { coreMock } from '../../../../../../src/core/public/mocks';
|
|||
import type { MetricsExplorerResponse } from '../../../common/http_api/metrics_explorer';
|
||||
import type { MetricsSourceConfigurationResponse } from '../../../common/metrics_sources';
|
||||
import type { CoreProvidersProps } from '../../apps/common_providers';
|
||||
import type { InfraClientStartDeps } from '../../types';
|
||||
import type {
|
||||
InfraClientStartDeps,
|
||||
InfraClientStartExports,
|
||||
InfraClientStartServices,
|
||||
} from '../../types';
|
||||
|
||||
export type SourceResponseMock = DeepPartial<MetricsSourceConfigurationResponse>;
|
||||
export type DataResponseMock = DeepPartial<MetricsExplorerResponse>;
|
||||
|
@ -20,19 +24,26 @@ export type NodeMetricsTableFetchMock = (
|
|||
options: HttpFetchOptions
|
||||
) => Promise<SourceResponseMock | DataResponseMock>;
|
||||
|
||||
export function createCoreProvidersPropsMock(fetchMock: NodeMetricsTableFetchMock) {
|
||||
export function createStartServicesAccessorMock(fetchMock: NodeMetricsTableFetchMock) {
|
||||
const core = coreMock.createStart();
|
||||
// @ts-expect-error core.http.fetch has overloads, Jest/TypeScript only picks the first definition when mocking
|
||||
core.http.fetch.mockImplementation(fetchMock);
|
||||
|
||||
const coreProvidersPropsMock: CoreProvidersProps = {
|
||||
core,
|
||||
pluginStart: {} as InfraClientStartExports,
|
||||
plugins: {} as InfraClientStartDeps,
|
||||
theme$: core.theme.theme$,
|
||||
};
|
||||
const getStartServices = (): InfraClientStartServices => [
|
||||
coreProvidersPropsMock.core,
|
||||
coreProvidersPropsMock.plugins,
|
||||
coreProvidersPropsMock.pluginStart,
|
||||
];
|
||||
|
||||
return {
|
||||
coreProvidersPropsMock,
|
||||
fetch: core.http.fetch,
|
||||
getStartServices,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,158 +1,6 @@
|
|||
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks';
|
||||
import { defer, of, Subject } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/public';
|
||||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { LOG_ENTRIES_SEARCH_STRATEGY } from '../../../common/search_strategies/log_entries/log_entries';
|
||||
import { createIndexPatternMock, createIndexPatternsMock } from '../../hooks/use_kibana_index_patterns.mock';
|
||||
import { DEFAULT_SOURCE_CONFIGURATION } from '../../test_utils/source_configuration';
|
||||
import { generateFakeEntries, ENTRIES_EMPTY } from '../../test_utils/entries';
|
||||
import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme';
|
||||
|
||||
import { LogStream } from './';
|
||||
|
||||
<!-- Prework -->
|
||||
|
||||
export const startTimestamp = 1595145600000;
|
||||
export const endTimestamp = startTimestamp + 15 * 60 * 1000;
|
||||
|
||||
export const dataMock = {
|
||||
indexPatterns: createIndexPatternsMock(500, [
|
||||
createIndexPatternMock({
|
||||
id: 'some-test-id',
|
||||
title: 'mock-index-pattern-*',
|
||||
timeFieldName: '@timestamp',
|
||||
fields: [
|
||||
{
|
||||
name: '@timestamp',
|
||||
type: KBN_FIELD_TYPES.DATE,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
{
|
||||
name: 'event.dataset',
|
||||
type: KBN_FIELD_TYPES.STRING,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
{
|
||||
name: 'host.name',
|
||||
type: KBN_FIELD_TYPES.STRING,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
{
|
||||
name: 'log.level',
|
||||
type: KBN_FIELD_TYPES.STRING,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: KBN_FIELD_TYPES.STRING,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
]),
|
||||
search: {
|
||||
search: ({ params }, options) => {
|
||||
return defer(() => {
|
||||
switch (options.strategy) {
|
||||
case LOG_ENTRIES_SEARCH_STRATEGY:
|
||||
if (params.after?.time === params.endTimestamp || params.before?.time === params.startTimestamp) {
|
||||
return of({
|
||||
id: 'EMPTY_FAKE_RESPONSE',
|
||||
total: 1,
|
||||
loaded: 1,
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
rawResponse: ENTRIES_EMPTY,
|
||||
});
|
||||
} else {
|
||||
const entries = generateFakeEntries(
|
||||
200,
|
||||
params.startTimestamp,
|
||||
params.endTimestamp,
|
||||
params.columns || DEFAULT_SOURCE_CONFIGURATION.data.configuration.logColumns
|
||||
);
|
||||
return of({
|
||||
id: 'FAKE_RESPONSE',
|
||||
total: 1,
|
||||
loaded: 1,
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
rawResponse: {
|
||||
data: {
|
||||
entries,
|
||||
topCursor: entries[0].cursor,
|
||||
bottomCursor: entries[entries.length - 1].cursor,
|
||||
hasMoreBefore: false,
|
||||
},
|
||||
errors: [],
|
||||
}
|
||||
});
|
||||
}
|
||||
default:
|
||||
return of({
|
||||
id: 'FAKE_RESPONSE',
|
||||
rawResponse: {},
|
||||
});
|
||||
}
|
||||
}).pipe(delay(2000));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const fetch = async function (url, params) {
|
||||
switch (url) {
|
||||
case '/api/infra/log_source_configurations/default':
|
||||
return DEFAULT_SOURCE_CONFIGURATION;
|
||||
case '/api/infra/log_source_configurations/default/status':
|
||||
return {
|
||||
data: {
|
||||
logIndexStatus: 'available',
|
||||
}
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const uiSettings = {
|
||||
get: (setting) => {
|
||||
switch (setting) {
|
||||
case 'dateFormat':
|
||||
return 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
case 'dateFormat:scaled':
|
||||
return [['', 'HH:mm:ss.SSS']];
|
||||
}
|
||||
},
|
||||
get$: () => {
|
||||
return new Subject();
|
||||
},
|
||||
};
|
||||
|
||||
export const Template = (args) => <LogStream {...args} />;
|
||||
|
||||
<Meta
|
||||
title="infra/LogStream"
|
||||
component={LogStream}
|
||||
decorators={[
|
||||
(story) => (
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={{ data: dataMock, http: { fetch }, uiSettings }}>
|
||||
{story()}
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
),
|
||||
decorateWithGlobalStorybookThemeProviders,
|
||||
]}
|
||||
/>
|
||||
<Meta title="infra/LogStream/Overview" />
|
||||
|
||||
# Embeddable `<LogStream />` component
|
||||
|
||||
|
@ -187,11 +35,7 @@ const startTimestamp = endTimestamp - 15 * 60 * 1000; // 15 minutes
|
|||
|
||||
This will show a list of log entries between the specified timestamps.
|
||||
|
||||
<Canvas>
|
||||
<Story name="Default" args={{ startTimestamp, endTimestamp }}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
<Story id="infra-logstream--basic-date-range" />
|
||||
|
||||
## Query log entries
|
||||
|
||||
|
@ -246,14 +90,7 @@ By default the component will load at the bottom of the list, showing the newest
|
|||
/>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="CenteredView"
|
||||
args={{ startTimestamp, endTimestamp, center: { time: 1595146275000, tiebreaker: 150 } }}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
<Story id="infra-logstream--centered-on-log-entry" />
|
||||
|
||||
## Highlight a specific entry
|
||||
|
||||
|
@ -263,11 +100,7 @@ The component can highlight a specific line via the `highlight` prop. It takes t
|
|||
<LogStream startTimestamp={startTimestamp} endTimestamp={endTimestamp} highlight="entry-197" />
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story name="HighlightedEntry" args={{ startTimestamp, endTimestamp, highlight: 'entry-197' }}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
<Story id="infra-logstream--highlighted-log-entry" />
|
||||
|
||||
## Column configuration
|
||||
|
||||
|
@ -298,23 +131,7 @@ The easiest way is to specify what columns you want with the `columns` prop.
|
|||
/>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="CustomColumns"
|
||||
args={{
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
columns: [
|
||||
{ type: 'timestamp' },
|
||||
{ type: 'field', field: 'log.level' },
|
||||
{ type: 'field', field: 'host.name' },
|
||||
{ type: 'message' },
|
||||
],
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
<Story id="infra-logstream--custom-columns" />
|
||||
|
||||
The rendering of the column headers and the cell contents can also be customized with the following properties:
|
||||
|
||||
|
@ -389,57 +206,25 @@ The rendering of the column headers and the cell contents can also be customized
|
|||
/>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="CustomColumnRendering"
|
||||
args={{
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
columns: [
|
||||
{ type: 'timestamp', header: 'When?' },
|
||||
{
|
||||
type: 'field',
|
||||
field: 'log.level',
|
||||
header: false,
|
||||
width: 24,
|
||||
render: (value) => {
|
||||
switch (value) {
|
||||
case 'debug':
|
||||
return '🐞';
|
||||
case 'info':
|
||||
return 'ℹ️';
|
||||
case 'warn':
|
||||
return '⚠️';
|
||||
case 'error':
|
||||
return '❌';
|
||||
}
|
||||
},
|
||||
},
|
||||
{ type: 'message' },
|
||||
],
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
<Story id="infra-logstream--custom-column-rendering" />
|
||||
|
||||
### With a source configuration
|
||||
### With a static log view configuration
|
||||
|
||||
The infra plugin has the concept of a "source configuration", a collection of settings that apply to the logs and metrics UIs. The component uses the source configuration to determine which indices to query or what columns to show.
|
||||
The infra plugin has the concept of a "log view", a collection of settings that apply to the logs UI. The component uses the log view to determine which indices to query or what columns to show.
|
||||
|
||||
The `<LogStream />` component will use the `"default"` source configuration. If you want to use your own configuration, you need to first create it when you initialize your plugin, and then specify it in the `<LogStream />` component with the `sourceId` prop.
|
||||
The `<LogStream />` component will use the `"default"` log view. If you want to use your own log view, you need to first create it when you initialize your plugin, and then specify it in the `<LogStream />` component with the `sourceId` prop.
|
||||
|
||||
```tsx
|
||||
// Your `server/plugin.ts`
|
||||
class MyPlugin {
|
||||
// ...
|
||||
setup(core, plugins) {
|
||||
plugins.infra.defineInternalSourceConfiguration(
|
||||
'my_source', // ID for your source configuration
|
||||
plugins.infra.logViews.defineInternalLogView(
|
||||
'my_log_view', // ID for your log view
|
||||
{
|
||||
name: 'some-name',
|
||||
description: 'some description',
|
||||
logIndices: { // Also accepts an `index_pattern` type with `indexPatternId`
|
||||
logIndices: { // Also accepts a `data_view` type with `dataViewId`
|
||||
type: 'index_name',
|
||||
indexName: 'some-index',
|
||||
},
|
||||
|
@ -463,4 +248,4 @@ class MyPlugin {
|
|||
|
||||
### Setting component height
|
||||
|
||||
It's possible to pass a `height` prop, e.g. `60vh` or `300px`, to specify how much vertical space the component should consume.
|
||||
It's possible to pass a `height` prop, e.g. `60vh` or `300px`, to specify how much vertical space the component should consume.
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import type { Meta, Story } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme';
|
||||
import { LogStream, LogStreamProps } from './log_stream';
|
||||
import { decorateWithKibanaContext } from './log_stream.story_decorators';
|
||||
|
||||
const startTimestamp = 1595145600000;
|
||||
const endTimestamp = startTimestamp + 15 * 60 * 1000;
|
||||
|
||||
export default {
|
||||
title: 'infra/LogStream',
|
||||
component: LogStream,
|
||||
decorators: [
|
||||
(wrappedStory) => <I18nProvider>{wrappedStory()}</I18nProvider>,
|
||||
decorateWithKibanaContext,
|
||||
decorateWithGlobalStorybookThemeProviders,
|
||||
],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
args: {
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const LogStreamStoryTemplate: Story<LogStreamProps> = (args) => <LogStream {...args} />;
|
||||
|
||||
export const BasicDateRange = LogStreamStoryTemplate.bind({});
|
||||
|
||||
export const CenteredOnLogEntry = LogStreamStoryTemplate.bind({});
|
||||
CenteredOnLogEntry.args = {
|
||||
center: { time: 1595146275000, tiebreaker: 150 },
|
||||
};
|
||||
|
||||
export const HighlightedLogEntry = LogStreamStoryTemplate.bind({});
|
||||
HighlightedLogEntry.args = {
|
||||
highlight: 'entry-197',
|
||||
};
|
||||
|
||||
export const CustomColumns = LogStreamStoryTemplate.bind({});
|
||||
CustomColumns.args = {
|
||||
columns: [
|
||||
{ type: 'timestamp' },
|
||||
{ type: 'field', field: 'log.level' },
|
||||
{ type: 'field', field: 'host.name' },
|
||||
{ type: 'message' },
|
||||
],
|
||||
};
|
||||
|
||||
export const CustomColumnRendering = LogStreamStoryTemplate.bind({});
|
||||
CustomColumnRendering.args = {
|
||||
columns: [
|
||||
{ type: 'timestamp', header: 'When?' },
|
||||
{
|
||||
type: 'field',
|
||||
field: 'log.level',
|
||||
header: false,
|
||||
width: 24,
|
||||
render: (value) => {
|
||||
switch (value) {
|
||||
case 'debug':
|
||||
return '🐞';
|
||||
case 'info':
|
||||
return 'ℹ️';
|
||||
case 'warn':
|
||||
return '⚠️';
|
||||
case 'error':
|
||||
return '❌';
|
||||
}
|
||||
},
|
||||
},
|
||||
{ type: 'message' },
|
||||
],
|
||||
};
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { StoryContext } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { defer, of, Subject } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import {
|
||||
ENHANCED_ES_SEARCH_STRATEGY,
|
||||
ES_SEARCH_STRATEGY,
|
||||
FieldSpec,
|
||||
} from '../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
IEsSearchResponse,
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse,
|
||||
ISearchOptions,
|
||||
} from '../../../../../../src/plugins/data/public';
|
||||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { getLogViewResponsePayloadRT } from '../../../common/http_api/log_views';
|
||||
import { defaultLogViewAttributes } from '../../../common/log_views';
|
||||
import {
|
||||
LogEntriesSearchResponsePayload,
|
||||
LOG_ENTRIES_SEARCH_STRATEGY,
|
||||
} from '../../../common/search_strategies/log_entries/log_entries';
|
||||
import { ENTRIES_EMPTY, generateFakeEntries } from '../../test_utils/entries';
|
||||
|
||||
export const decorateWithKibanaContext = <StoryFnReactReturnType extends React.ReactNode>(
|
||||
wrappedStory: () => StoryFnReactReturnType,
|
||||
_storyContext: StoryContext
|
||||
) => {
|
||||
const data = {
|
||||
dataViews: {
|
||||
getFieldsForWildcard: async (): Promise<FieldSpec[]> => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
search: {
|
||||
search: ({ params }: IKibanaSearchRequest, options?: ISearchOptions) => {
|
||||
return defer(() => {
|
||||
switch (options?.strategy) {
|
||||
case LOG_ENTRIES_SEARCH_STRATEGY:
|
||||
if (
|
||||
params.after?.time === params.endTimestamp ||
|
||||
params.before?.time === params.startTimestamp
|
||||
) {
|
||||
return of<IKibanaSearchResponse<LogEntriesSearchResponsePayload>>({
|
||||
id: 'MOCK_LOG_ENTRIES_RESPONSE',
|
||||
total: 1,
|
||||
loaded: 1,
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
rawResponse: ENTRIES_EMPTY,
|
||||
});
|
||||
} else {
|
||||
const entries = generateFakeEntries(
|
||||
200,
|
||||
params.startTimestamp,
|
||||
params.endTimestamp,
|
||||
params.columns || defaultLogViewAttributes.logColumns
|
||||
);
|
||||
return of<IKibanaSearchResponse<LogEntriesSearchResponsePayload>>({
|
||||
id: 'MOCK_LOG_ENTRIES_RESPONSE',
|
||||
total: 1,
|
||||
loaded: 1,
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
rawResponse: {
|
||||
data: {
|
||||
entries,
|
||||
topCursor: entries[0].cursor,
|
||||
bottomCursor: entries[entries.length - 1].cursor,
|
||||
hasMoreBefore: false,
|
||||
},
|
||||
errors: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
case undefined:
|
||||
case ES_SEARCH_STRATEGY:
|
||||
case ENHANCED_ES_SEARCH_STRATEGY:
|
||||
return of<IEsSearchResponse>({
|
||||
id: 'MOCK_INDEX_CHECK_RESPONSE',
|
||||
total: 1,
|
||||
loaded: 1,
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
rawResponse: {
|
||||
_shards: {
|
||||
failed: 0,
|
||||
successful: 1,
|
||||
total: 1,
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
total: 1,
|
||||
},
|
||||
timed_out: false,
|
||||
took: 1,
|
||||
},
|
||||
});
|
||||
default:
|
||||
return of<IKibanaSearchResponse>({
|
||||
id: 'FAKE_RESPONSE',
|
||||
rawResponse: {},
|
||||
});
|
||||
}
|
||||
}).pipe(delay(2000));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const http = {
|
||||
get: async (path: string) => {
|
||||
switch (path) {
|
||||
case '/api/infra/log_views/default':
|
||||
return getLogViewResponsePayloadRT.encode({
|
||||
data: {
|
||||
id: 'default',
|
||||
origin: 'stored',
|
||||
attributes: defaultLogViewAttributes,
|
||||
},
|
||||
});
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const uiSettings = {
|
||||
get: (setting: string) => {
|
||||
switch (setting) {
|
||||
case 'dateFormat':
|
||||
return 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
case 'dateFormat:scaled':
|
||||
return [['', 'HH:mm:ss.SSS']];
|
||||
}
|
||||
},
|
||||
get$: () => new Subject(),
|
||||
};
|
||||
|
||||
return (
|
||||
<KibanaContextProvider services={{ data, http, uiSettings }}>
|
||||
{wrappedStory()}
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
|
@ -13,8 +13,10 @@ import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public
|
|||
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { LogEntryCursor } from '../../../common/log_entry';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import { defaultLogViewsStaticConfig } from '../../../common/log_views';
|
||||
import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream';
|
||||
import { useLogView } from '../../hooks/use_log_view';
|
||||
import { LogViewsClient } from '../../services/log_views';
|
||||
import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration';
|
||||
import { useKibanaQuerySettings } from '../../utils/use_kibana_query_settings';
|
||||
import { ScrollableLogTextStreamView } from '../logging/log_text_stream';
|
||||
|
@ -97,9 +99,10 @@ export const LogStreamContent: React.FC<LogStreamContentProps> = ({
|
|||
[columns]
|
||||
);
|
||||
|
||||
// source boilerplate
|
||||
const { services } = useKibana<LogStreamPluginDeps>();
|
||||
if (!services?.http?.fetch || !services?.data?.indexPatterns) {
|
||||
const {
|
||||
services: { http, data },
|
||||
} = useKibana<LogStreamPluginDeps>();
|
||||
if (http == null || data == null) {
|
||||
throw new Error(
|
||||
`<LogStream /> cannot access kibana core services.
|
||||
|
||||
|
@ -111,32 +114,37 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
|
||||
const kibanaQuerySettings = useKibanaQuerySettings();
|
||||
|
||||
const logViews = useMemo(
|
||||
() => new LogViewsClient(data.dataViews, http, data.search.search, defaultLogViewsStaticConfig),
|
||||
[data.dataViews, data.search.search, http]
|
||||
);
|
||||
|
||||
const {
|
||||
derivedIndexPattern,
|
||||
isLoading: isLoadingSource,
|
||||
loadSource,
|
||||
sourceConfiguration,
|
||||
} = useLogSource({
|
||||
sourceId,
|
||||
fetch: services.http.fetch,
|
||||
indexPatternsService: services.data.indexPatterns,
|
||||
derivedDataView,
|
||||
isLoading: isLoadingLogView,
|
||||
load: loadLogView,
|
||||
resolvedLogView,
|
||||
} = useLogView({
|
||||
logViewId: sourceId,
|
||||
logViews,
|
||||
fetch: http.fetch,
|
||||
});
|
||||
|
||||
const parsedQuery = useMemo<BuiltEsQuery | undefined>(() => {
|
||||
if (typeof query === 'object' && 'bool' in query) {
|
||||
return mergeBoolQueries(
|
||||
query,
|
||||
buildEsQuery(derivedIndexPattern, [], filters ?? [], kibanaQuerySettings)
|
||||
buildEsQuery(derivedDataView, [], filters ?? [], kibanaQuerySettings)
|
||||
);
|
||||
} else {
|
||||
return buildEsQuery(
|
||||
derivedIndexPattern,
|
||||
derivedDataView,
|
||||
coerceToQueries(query),
|
||||
filters ?? [],
|
||||
kibanaQuerySettings
|
||||
);
|
||||
}
|
||||
}, [derivedIndexPattern, filters, kibanaQuerySettings, query]);
|
||||
}, [derivedDataView, filters, kibanaQuerySettings, query]);
|
||||
|
||||
// Internal state
|
||||
const {
|
||||
|
@ -158,8 +166,8 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
});
|
||||
|
||||
const columnConfigurations = useMemo(() => {
|
||||
return sourceConfiguration ? customColumns ?? sourceConfiguration.configuration.logColumns : [];
|
||||
}, [sourceConfiguration, customColumns]);
|
||||
return resolvedLogView ? customColumns ?? resolvedLogView.columns : [];
|
||||
}, [resolvedLogView, customColumns]);
|
||||
|
||||
const streamItems = useMemo(
|
||||
() =>
|
||||
|
@ -173,8 +181,8 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
|
||||
// Component lifetime
|
||||
useEffect(() => {
|
||||
loadSource();
|
||||
}, [loadSource]);
|
||||
loadLogView();
|
||||
}, [loadLogView]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntries();
|
||||
|
@ -207,7 +215,7 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
items={streamItems}
|
||||
scale="medium"
|
||||
wrap={true}
|
||||
isReloading={isLoadingSource || isLoadingEntries}
|
||||
isReloading={isLoadingLogView || isLoadingEntries}
|
||||
isLoadingMore={isLoadingMore}
|
||||
hasMoreBeforeStart={hasMoreBefore}
|
||||
hasMoreAfterEnd={hasMoreAfter}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '../../../../../../src/plugins/embeddable/public';
|
||||
import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
|
||||
import { CoreProviders } from '../../apps/common_providers';
|
||||
import { InfraClientStartDeps } from '../../types';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../../types';
|
||||
import { datemathToEpochMillis } from '../../utils/datemath';
|
||||
import { LazyLogStreamWrapper } from './lazy_log_stream_wrapper';
|
||||
|
||||
|
@ -38,6 +38,7 @@ export class LogStreamEmbeddable extends Embeddable<LogStreamEmbeddableInput> {
|
|||
constructor(
|
||||
private core: CoreStart,
|
||||
private pluginDeps: InfraClientStartDeps,
|
||||
private pluginStart: InfraClientStartExports,
|
||||
initialInput: LogStreamEmbeddableInput,
|
||||
parent?: IContainer
|
||||
) {
|
||||
|
@ -78,7 +79,12 @@ export class LogStreamEmbeddable extends Embeddable<LogStreamEmbeddableInput> {
|
|||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<CoreProviders core={this.core} plugins={this.pluginDeps} theme$={this.core.theme.theme$}>
|
||||
<CoreProviders
|
||||
core={this.core}
|
||||
plugins={this.pluginDeps}
|
||||
pluginStart={this.pluginStart}
|
||||
theme$={this.core.theme.theme$}
|
||||
>
|
||||
<EuiThemeProvider>
|
||||
<div style={{ width: '100%' }}>
|
||||
<LazyLogStreamWrapper
|
||||
|
|
|
@ -5,13 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { StartServicesAccessor } from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EmbeddableFactoryDefinition,
|
||||
IContainer,
|
||||
} from '../../../../../../src/plugins/embeddable/public';
|
||||
import { InfraClientStartDeps } from '../../types';
|
||||
import { InfraClientStartServicesAccessor } from '../../types';
|
||||
import {
|
||||
LogStreamEmbeddable,
|
||||
LogStreamEmbeddableInput,
|
||||
|
@ -23,7 +22,7 @@ export class LogStreamEmbeddableFactoryDefinition
|
|||
{
|
||||
public readonly type = LOG_STREAM_EMBEDDABLE;
|
||||
|
||||
constructor(private getStartServices: StartServicesAccessor<InfraClientStartDeps>) {}
|
||||
constructor(private getStartServices: InfraClientStartServicesAccessor) {}
|
||||
|
||||
public async isEditable() {
|
||||
const [{ application }] = await this.getStartServices();
|
||||
|
@ -31,8 +30,8 @@ export class LogStreamEmbeddableFactoryDefinition
|
|||
}
|
||||
|
||||
public async create(initialInput: LogStreamEmbeddableInput, parent?: IContainer) {
|
||||
const [core, plugins] = await this.getStartServices();
|
||||
return new LogStreamEmbeddable(core, plugins, initialInput, parent);
|
||||
const [core, plugins, pluginStart] = await this.getStartServices();
|
||||
return new LogStreamEmbeddable(core, plugins, pluginStart, initialInput, parent);
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
|
|
|
@ -9,12 +9,12 @@ import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, EuiSpacer } from
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
import { SavedObjectNotFound } from '../../../../../../src/plugins/kibana_utils/common';
|
||||
import {
|
||||
FetchLogSourceConfigurationError,
|
||||
FetchLogSourceStatusError,
|
||||
ResolveLogSourceConfigurationError,
|
||||
} from '../../../common/log_sources';
|
||||
import { useLinkProps } from '../../../../observability/public';
|
||||
import {
|
||||
FetchLogViewStatusError,
|
||||
FetchLogViewError,
|
||||
ResolveLogViewError,
|
||||
} from '../../../common/log_views';
|
||||
import { LogsPageTemplate } from '../../pages/logs/page_template';
|
||||
|
||||
export const LogSourceErrorPage: React.FC<{
|
||||
|
@ -72,7 +72,7 @@ export const LogSourceErrorPage: React.FC<{
|
|||
};
|
||||
|
||||
const LogSourceErrorMessage: React.FC<{ error: Error }> = ({ error }) => {
|
||||
if (error instanceof ResolveLogSourceConfigurationError) {
|
||||
if (error instanceof ResolveLogViewError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
|
@ -97,7 +97,7 @@ const LogSourceErrorMessage: React.FC<{ error: Error }> = ({ error }) => {
|
|||
)}
|
||||
</LogSourceErrorCallout>
|
||||
);
|
||||
} else if (error instanceof FetchLogSourceConfigurationError) {
|
||||
} else if (error instanceof FetchLogViewError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
|
@ -110,7 +110,7 @@ const LogSourceErrorMessage: React.FC<{ error: Error }> = ({ error }) => {
|
|||
{`${error.cause?.message ?? error.message}`}
|
||||
</LogSourceErrorCallout>
|
||||
);
|
||||
} else if (error instanceof FetchLogSourceStatusError) {
|
||||
} else if (error instanceof FetchLogViewStatusError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import {
|
||||
getLogSourceConfigurationPath,
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { FetchLogSourceConfigurationError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callFetchLogSourceConfigurationAPI = async (sourceId: string, fetch: HttpHandler) => {
|
||||
const response = await fetch(getLogSourceConfigurationPath(sourceId), {
|
||||
method: 'GET',
|
||||
}).catch((error) => {
|
||||
throw new FetchLogSourceConfigurationError(
|
||||
`Failed to fetch log source configuration "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new FetchLogSourceConfigurationError(
|
||||
`Failed to decode log source configuration "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import {
|
||||
getLogSourceStatusPath,
|
||||
getLogSourceStatusSuccessResponsePayloadRT,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { FetchLogSourceStatusError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callFetchLogSourceStatusAPI = async (sourceId: string, fetch: HttpHandler) => {
|
||||
const response = await fetch(getLogSourceStatusPath(sourceId), {
|
||||
method: 'GET',
|
||||
}).catch((error) => {
|
||||
throw new FetchLogSourceStatusError(
|
||||
`Failed to fetch status for log source "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(
|
||||
getLogSourceStatusSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new FetchLogSourceStatusError(
|
||||
`Failed to decode status for log source "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import {
|
||||
getLogSourceConfigurationPath,
|
||||
patchLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
patchLogSourceConfigurationRequestBodyRT,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { PatchLogSourceConfigurationError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callPatchLogSourceConfigurationAPI = async (
|
||||
sourceId: string,
|
||||
patchedProperties: LogSourceConfigurationPropertiesPatch,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const response = await fetch(getLogSourceConfigurationPath(sourceId), {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(
|
||||
patchLogSourceConfigurationRequestBodyRT.encode({
|
||||
data: patchedProperties,
|
||||
})
|
||||
),
|
||||
}).catch((error) => {
|
||||
throw new PatchLogSourceConfigurationError(
|
||||
`Failed to update log source configuration "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(
|
||||
patchLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new PatchLogSourceConfigurationError(
|
||||
`Failed to decode log source configuration "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogSourceConfiguration, LogSourceStatus, useLogSource } from './log_source';
|
||||
|
||||
type CreateUseLogSource = (sourceConfiguration?: { sourceId?: string }) => typeof useLogSource;
|
||||
|
||||
const defaultSourceId = 'default';
|
||||
|
||||
export const createUninitializedUseLogSourceMock: CreateUseLogSource =
|
||||
({ sourceId = defaultSourceId } = {}) =>
|
||||
() => ({
|
||||
derivedIndexPattern: {
|
||||
fields: [],
|
||||
title: 'unknown',
|
||||
},
|
||||
hasFailedLoading: false,
|
||||
hasFailedLoadingSource: false,
|
||||
hasFailedLoadingSourceStatus: false,
|
||||
hasFailedResolvingSource: false,
|
||||
initialize: jest.fn(),
|
||||
isLoading: false,
|
||||
isLoadingSourceConfiguration: false,
|
||||
isLoadingSourceStatus: false,
|
||||
isResolvingSourceConfiguration: false,
|
||||
isUninitialized: true,
|
||||
loadSource: jest.fn(),
|
||||
loadSourceConfiguration: jest.fn(),
|
||||
latestLoadSourceFailures: [],
|
||||
resolveSourceFailureMessage: undefined,
|
||||
loadSourceStatus: jest.fn(),
|
||||
sourceConfiguration: undefined,
|
||||
sourceId,
|
||||
sourceStatus: undefined,
|
||||
updateSource: jest.fn(),
|
||||
resolvedSourceConfiguration: undefined,
|
||||
loadResolveLogSourceConfiguration: jest.fn(),
|
||||
});
|
||||
|
||||
export const createLoadingUseLogSourceMock: CreateUseLogSource =
|
||||
({ sourceId = defaultSourceId } = {}) =>
|
||||
(args) => ({
|
||||
...createUninitializedUseLogSourceMock({ sourceId })(args),
|
||||
isLoading: true,
|
||||
isLoadingSourceConfiguration: true,
|
||||
isLoadingSourceStatus: true,
|
||||
isResolvingSourceConfiguration: true,
|
||||
});
|
||||
|
||||
export const createLoadedUseLogSourceMock: CreateUseLogSource =
|
||||
({ sourceId = defaultSourceId } = {}) =>
|
||||
(args) => ({
|
||||
...createUninitializedUseLogSourceMock({ sourceId })(args),
|
||||
sourceConfiguration: createBasicSourceConfiguration(sourceId),
|
||||
sourceStatus: {
|
||||
indices: 'test-index',
|
||||
logIndexStatus: 'available',
|
||||
},
|
||||
});
|
||||
|
||||
export const createBasicSourceConfiguration = (sourceId: string): LogSourceConfiguration => ({
|
||||
id: sourceId,
|
||||
origin: 'stored',
|
||||
configuration: {
|
||||
description: `description for ${sourceId}`,
|
||||
logIndices: {
|
||||
type: 'index_pattern',
|
||||
indexPatternId: 'some-id',
|
||||
},
|
||||
logColumns: [],
|
||||
fields: {
|
||||
message: ['MESSAGE_FIELD'],
|
||||
},
|
||||
name: sourceId,
|
||||
},
|
||||
});
|
||||
|
||||
export const createAvailableSourceStatus = (): LogSourceStatus => ({
|
||||
indices: 'test-index',
|
||||
logIndexStatus: 'available',
|
||||
});
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import { DataViewsContract } from '../../../../../../../src/plugins/data_views/public';
|
||||
import {
|
||||
LogIndexField,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
LogSourceStatus,
|
||||
} from '../../../../common/http_api/log_sources';
|
||||
import {
|
||||
LogSourceConfiguration,
|
||||
LogSourceConfigurationProperties,
|
||||
ResolvedLogSourceConfiguration,
|
||||
resolveLogSourceConfiguration,
|
||||
ResolveLogSourceConfigurationError,
|
||||
} from '../../../../common/log_sources';
|
||||
import { isRejectedPromiseState, useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_configuration';
|
||||
import { callFetchLogSourceStatusAPI } from './api/fetch_log_source_status';
|
||||
import { callPatchLogSourceConfigurationAPI } from './api/patch_log_source_configuration';
|
||||
|
||||
export type {
|
||||
LogIndexField,
|
||||
LogSourceConfiguration,
|
||||
LogSourceConfigurationProperties,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
LogSourceStatus,
|
||||
};
|
||||
export { ResolveLogSourceConfigurationError };
|
||||
|
||||
export const useLogSource = ({
|
||||
sourceId,
|
||||
fetch,
|
||||
indexPatternsService,
|
||||
}: {
|
||||
sourceId: string;
|
||||
fetch: HttpHandler;
|
||||
indexPatternsService: DataViewsContract;
|
||||
}) => {
|
||||
const [sourceConfiguration, setSourceConfiguration] = useState<
|
||||
LogSourceConfiguration | undefined
|
||||
>(undefined);
|
||||
|
||||
const [resolvedSourceConfiguration, setResolvedSourceConfiguration] = useState<
|
||||
ResolvedLogSourceConfiguration | undefined
|
||||
>(undefined);
|
||||
|
||||
const [sourceStatus, setSourceStatus] = useState<LogSourceStatus | undefined>(undefined);
|
||||
|
||||
const [loadSourceConfigurationRequest, loadSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
return (await callFetchLogSourceConfigurationAPI(sourceId, fetch)).data;
|
||||
},
|
||||
onResolve: setSourceConfiguration,
|
||||
},
|
||||
[sourceId, fetch, indexPatternsService]
|
||||
);
|
||||
|
||||
const [resolveSourceConfigurationRequest, resolveSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async (unresolvedSourceConfiguration: LogSourceConfigurationProperties) => {
|
||||
return await resolveLogSourceConfiguration(
|
||||
unresolvedSourceConfiguration,
|
||||
indexPatternsService
|
||||
);
|
||||
},
|
||||
onResolve: setResolvedSourceConfiguration,
|
||||
},
|
||||
[indexPatternsService]
|
||||
);
|
||||
|
||||
const [updateSourceConfigurationRequest, updateSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async (patchedProperties: LogSourceConfigurationPropertiesPatch) => {
|
||||
return (await callPatchLogSourceConfigurationAPI(sourceId, patchedProperties, fetch)).data;
|
||||
},
|
||||
onResolve: setSourceConfiguration,
|
||||
},
|
||||
[sourceId, fetch, indexPatternsService]
|
||||
);
|
||||
|
||||
const [loadSourceStatusRequest, loadSourceStatus] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
return await callFetchLogSourceStatusAPI(sourceId, fetch);
|
||||
},
|
||||
onResolve: ({ data }) => setSourceStatus(data),
|
||||
},
|
||||
[sourceId, fetch]
|
||||
);
|
||||
|
||||
const derivedIndexPattern = useMemo(
|
||||
() => ({
|
||||
fields: resolvedSourceConfiguration?.fields ?? [],
|
||||
title: resolvedSourceConfiguration?.indices ?? 'unknown',
|
||||
}),
|
||||
[resolvedSourceConfiguration]
|
||||
);
|
||||
|
||||
const isLoadingSourceConfiguration = loadSourceConfigurationRequest.state === 'pending';
|
||||
const isResolvingSourceConfiguration = resolveSourceConfigurationRequest.state === 'pending';
|
||||
const isLoadingSourceStatus = loadSourceStatusRequest.state === 'pending';
|
||||
const isUpdatingSourceConfiguration = updateSourceConfigurationRequest.state === 'pending';
|
||||
|
||||
const isLoading =
|
||||
isLoadingSourceConfiguration ||
|
||||
isResolvingSourceConfiguration ||
|
||||
isLoadingSourceStatus ||
|
||||
isUpdatingSourceConfiguration;
|
||||
|
||||
const isUninitialized =
|
||||
loadSourceConfigurationRequest.state === 'uninitialized' ||
|
||||
resolveSourceConfigurationRequest.state === 'uninitialized' ||
|
||||
loadSourceStatusRequest.state === 'uninitialized';
|
||||
|
||||
const hasFailedLoadingSource = loadSourceConfigurationRequest.state === 'rejected';
|
||||
const hasFailedResolvingSource = resolveSourceConfigurationRequest.state === 'rejected';
|
||||
const hasFailedLoadingSourceStatus = loadSourceStatusRequest.state === 'rejected';
|
||||
|
||||
const latestLoadSourceFailures = [
|
||||
loadSourceConfigurationRequest,
|
||||
resolveSourceConfigurationRequest,
|
||||
loadSourceStatusRequest,
|
||||
]
|
||||
.filter(isRejectedPromiseState)
|
||||
.map(({ value }) => (value instanceof Error ? value : new Error(`${value}`)));
|
||||
|
||||
const hasFailedLoading = latestLoadSourceFailures.length > 0;
|
||||
|
||||
const loadSource = useCallback(async () => {
|
||||
const loadSourceConfigurationPromise = loadSourceConfiguration();
|
||||
const loadSourceStatusPromise = loadSourceStatus();
|
||||
const resolveSourceConfigurationPromise = resolveSourceConfiguration(
|
||||
(await loadSourceConfigurationPromise).configuration
|
||||
);
|
||||
|
||||
return await Promise.all([
|
||||
loadSourceConfigurationPromise,
|
||||
resolveSourceConfigurationPromise,
|
||||
loadSourceStatusPromise,
|
||||
]);
|
||||
}, [loadSourceConfiguration, loadSourceStatus, resolveSourceConfiguration]);
|
||||
|
||||
const updateSource = useCallback(
|
||||
async (patchedProperties: LogSourceConfigurationPropertiesPatch) => {
|
||||
const updatedSourceConfiguration = await updateSourceConfiguration(patchedProperties);
|
||||
const resolveSourceConfigurationPromise = resolveSourceConfiguration(
|
||||
updatedSourceConfiguration.configuration
|
||||
);
|
||||
const loadSourceStatusPromise = loadSourceStatus();
|
||||
|
||||
return await Promise.all([
|
||||
updatedSourceConfiguration,
|
||||
resolveSourceConfigurationPromise,
|
||||
loadSourceStatusPromise,
|
||||
]);
|
||||
},
|
||||
[loadSourceStatus, resolveSourceConfiguration, updateSourceConfiguration]
|
||||
);
|
||||
|
||||
const initialize = useCallback(async () => {
|
||||
if (!isUninitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await loadSource();
|
||||
}, [isUninitialized, loadSource]);
|
||||
|
||||
return {
|
||||
sourceId,
|
||||
initialize,
|
||||
isUninitialized,
|
||||
derivedIndexPattern,
|
||||
// Failure states
|
||||
hasFailedLoading,
|
||||
hasFailedLoadingSource,
|
||||
hasFailedLoadingSourceStatus,
|
||||
hasFailedResolvingSource,
|
||||
latestLoadSourceFailures,
|
||||
// Loading states
|
||||
isLoading,
|
||||
isLoadingSourceConfiguration,
|
||||
isLoadingSourceStatus,
|
||||
isResolvingSourceConfiguration,
|
||||
// Source status (denotes the state of the indices, e.g. missing)
|
||||
sourceStatus,
|
||||
loadSourceStatus,
|
||||
// Source configuration (represents the raw attributes of the source configuration)
|
||||
loadSource,
|
||||
sourceConfiguration,
|
||||
updateSource,
|
||||
// Resolved source configuration (represents a fully resolved state, you would use this for the vast majority of "read" scenarios)
|
||||
resolvedSourceConfiguration,
|
||||
};
|
||||
};
|
||||
|
||||
export const [LogSourceProvider, useLogSourceContext] = createContainer(useLogSource);
|
|
@ -12,8 +12,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import useSetState from 'react-use/lib/useSetState';
|
||||
import { LogEntry, LogEntryCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { useSubscription } from '../../../utils/use_observable';
|
||||
import { LogSourceConfigurationProperties } from '../log_source';
|
||||
import { useFetchLogEntriesAfter } from './use_fetch_log_entries_after';
|
||||
import { useFetchLogEntriesAround } from './use_fetch_log_entries_around';
|
||||
import { useFetchLogEntriesBefore } from './use_fetch_log_entries_before';
|
||||
|
@ -26,7 +26,7 @@ interface LogStreamProps {
|
|||
endTimestamp: number;
|
||||
query?: BuiltEsQuery;
|
||||
center?: LogEntryCursor;
|
||||
columns?: LogSourceConfigurationProperties['logColumns'];
|
||||
columns?: LogViewColumnConfiguration[];
|
||||
}
|
||||
|
||||
interface LogStreamState {
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
import { useCallback } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { exhaustMap } from 'rxjs/operators';
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
import { IKibanaSearchRequest } from '../../../../../../../src/plugins/data/public';
|
||||
import { LogSourceColumnConfiguration } from '../../../../common/log_sources';
|
||||
import { LogEntryAfterCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../../common/runtime_types';
|
||||
import {
|
||||
logEntriesSearchRequestParamsRT,
|
||||
|
@ -37,7 +37,7 @@ export const useLogEntriesAfterRequest = ({
|
|||
sourceId,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogSourceColumnConfiguration[];
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
|
@ -110,7 +110,7 @@ export const useFetchLogEntriesAfter = ({
|
|||
sourceId,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogSourceColumnConfiguration[];
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import { useCallback } from 'react';
|
||||
import { combineLatest, Observable, ReplaySubject } from 'rxjs';
|
||||
import { last, map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { LogSourceColumnConfiguration } from '../../../../common/log_sources';
|
||||
import { LogEntryCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { LogEntriesSearchRequestQuery } from '../../../../common/search_strategies/log_entries/log_entries';
|
||||
import { flattenDataSearchResponseDescriptor } from '../../../utils/data_search';
|
||||
import { useObservable, useObservableState } from '../../../utils/use_observable';
|
||||
|
@ -24,7 +24,7 @@ export const useFetchLogEntriesAround = ({
|
|||
sourceId,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogSourceColumnConfiguration[];
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
import { useCallback } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { exhaustMap } from 'rxjs/operators';
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
import { IKibanaSearchRequest } from '../../../../../../../src/plugins/data/public';
|
||||
import { LogSourceColumnConfiguration } from '../../../../common/log_sources';
|
||||
import { LogEntryBeforeCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../../common/runtime_types';
|
||||
import {
|
||||
logEntriesSearchRequestParamsRT,
|
||||
|
@ -37,7 +37,7 @@ export const useLogEntriesBeforeRequest = ({
|
|||
sourceId,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogSourceColumnConfiguration[];
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
|
@ -109,7 +109,7 @@ export const useFetchLogEntriesBefore = ({
|
|||
sourceId,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogSourceColumnConfiguration[];
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import { useContext } from 'react';
|
||||
import useThrottle from 'react-use/lib/useThrottle';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { RendererFunction } from '../../../utils/typed_react';
|
||||
import { LogFilterState } from '../log_filter';
|
||||
import { LogPositionState } from '../log_position';
|
||||
import { useLogSourceContext } from '../log_source';
|
||||
import { LogSummaryBuckets, useLogSummary } from './log_summary';
|
||||
|
||||
const FETCH_THROTTLE_INTERVAL = 3000;
|
||||
|
@ -24,7 +24,7 @@ export const WithSummary = ({
|
|||
end: number | null;
|
||||
}>;
|
||||
}) => {
|
||||
const { sourceId } = useLogSourceContext();
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { filterQuery } = useContext(LogFilterState.Context);
|
||||
const { startTimestamp, endTimestamp } = useContext(LogPositionState.Context);
|
||||
|
||||
|
@ -33,7 +33,7 @@ export const WithSummary = ({
|
|||
const throttledEndTimestamp = useThrottle(endTimestamp, FETCH_THROTTLE_INTERVAL);
|
||||
|
||||
const { buckets, start, end } = useLogSummary(
|
||||
sourceId,
|
||||
logViewId,
|
||||
throttledStartTimestamp,
|
||||
throttledEndTimestamp,
|
||||
filterQuery?.serializedQuery ?? null
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
import {
|
||||
createKibanaReactContext,
|
||||
KibanaReactContextValue,
|
||||
useKibana,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { InfraClientStartDeps } from '../types';
|
||||
|
||||
export type PluginKibanaContextValue = CoreStart & InfraClientStartDeps;
|
||||
|
||||
export const createKibanaContextForPlugin = (core: CoreStart, pluginsStart: InfraClientStartDeps) =>
|
||||
createKibanaReactContext<PluginKibanaContextValue>({
|
||||
...core,
|
||||
...pluginsStart,
|
||||
});
|
||||
|
||||
export const useKibanaContextForPlugin =
|
||||
useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>;
|
65
x-pack/plugins/infra/public/hooks/use_kibana.tsx
Normal file
65
x-pack/plugins/infra/public/hooks/use_kibana.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PropsOf } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
import {
|
||||
createKibanaReactContext,
|
||||
KibanaReactContextValue,
|
||||
useKibana,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { InfraClientCoreSetup, InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
|
||||
export type PluginKibanaContextValue = CoreStart & InfraClientStartDeps & InfraClientStartExports;
|
||||
|
||||
export const createKibanaContextForPlugin = (
|
||||
core: CoreStart,
|
||||
plugins: InfraClientStartDeps,
|
||||
pluginStart: InfraClientStartExports
|
||||
) =>
|
||||
createKibanaReactContext<PluginKibanaContextValue>({
|
||||
...core,
|
||||
...plugins,
|
||||
...pluginStart,
|
||||
});
|
||||
|
||||
export const useKibanaContextForPlugin =
|
||||
useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>;
|
||||
|
||||
export const useKibanaContextForPluginProvider = (
|
||||
core: CoreStart,
|
||||
plugins: InfraClientStartDeps,
|
||||
pluginStart: InfraClientStartExports
|
||||
) => {
|
||||
const { Provider } = useMemo(
|
||||
() => createKibanaContextForPlugin(core, plugins, pluginStart),
|
||||
[core, pluginStart, plugins]
|
||||
);
|
||||
|
||||
return Provider;
|
||||
};
|
||||
|
||||
export const createLazyComponentWithKibanaContext = <T extends React.ComponentType<any>>(
|
||||
coreSetup: InfraClientCoreSetup,
|
||||
lazyComponentFactory: () => Promise<{ default: T }>
|
||||
) =>
|
||||
React.lazy(() =>
|
||||
Promise.all([lazyComponentFactory(), coreSetup.getStartServices()]).then(
|
||||
([{ default: LazilyLoadedComponent }, [core, plugins, pluginStart]]) => {
|
||||
const { Provider } = createKibanaContextForPlugin(core, plugins, pluginStart);
|
||||
|
||||
return {
|
||||
default: (props: PropsOf<T>) => (
|
||||
<Provider>
|
||||
<LazilyLoadedComponent {...props} />
|
||||
</Provider>
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
66
x-pack/plugins/infra/public/hooks/use_log_view.mock.ts
Normal file
66
x-pack/plugins/infra/public/hooks/use_log_view.mock.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createLogViewMock } from '../../common/log_views/log_view.mock';
|
||||
import { createResolvedLogViewMockFromAttributes } from '../../common/log_views/resolved_log_view.mock';
|
||||
import { useLogView } from './use_log_view';
|
||||
|
||||
type UseLogView = typeof useLogView;
|
||||
type IUseLogView = ReturnType<UseLogView>;
|
||||
|
||||
const defaultLogViewId = 'default';
|
||||
|
||||
export const createUninitializedUseLogViewMock =
|
||||
(logViewId: string = defaultLogViewId) =>
|
||||
(): IUseLogView => ({
|
||||
derivedDataView: {
|
||||
fields: [],
|
||||
title: 'unknown',
|
||||
},
|
||||
hasFailedLoading: false,
|
||||
hasFailedLoadingLogView: false,
|
||||
hasFailedLoadingLogViewStatus: false,
|
||||
hasFailedResolvingLogView: false,
|
||||
isLoading: false,
|
||||
isLoadingLogView: false,
|
||||
isLoadingLogViewStatus: false,
|
||||
isResolvingLogView: false,
|
||||
isUninitialized: true,
|
||||
latestLoadLogViewFailures: [],
|
||||
load: jest.fn(),
|
||||
logView: undefined,
|
||||
logViewId,
|
||||
logViewStatus: undefined,
|
||||
resolvedLogView: undefined,
|
||||
update: jest.fn(),
|
||||
});
|
||||
|
||||
export const createLoadingUseLogViewMock =
|
||||
(logViewId: string = defaultLogViewId) =>
|
||||
(): IUseLogView => ({
|
||||
...createUninitializedUseLogViewMock(logViewId)(),
|
||||
isLoading: true,
|
||||
isLoadingLogView: true,
|
||||
isLoadingLogViewStatus: true,
|
||||
isResolvingLogView: true,
|
||||
});
|
||||
|
||||
export const createLoadedUseLogViewMock = async (logViewId: string = defaultLogViewId) => {
|
||||
const logView = createLogViewMock(logViewId);
|
||||
const resolvedLogView = await createResolvedLogViewMockFromAttributes(logView.attributes);
|
||||
|
||||
return (): IUseLogView => {
|
||||
return {
|
||||
...createUninitializedUseLogViewMock(logViewId)(),
|
||||
logView,
|
||||
resolvedLogView,
|
||||
logViewStatus: {
|
||||
index: 'available',
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
150
x-pack/plugins/infra/public/hooks/use_log_view.ts
Normal file
150
x-pack/plugins/infra/public/hooks/use_log_view.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import { LogView, LogViewAttributes, LogViewStatus, ResolvedLogView } from '../../common/log_views';
|
||||
import type { ILogViewsClient } from '../services/log_views';
|
||||
import { isRejectedPromiseState, useTrackedPromise } from '../utils/use_tracked_promise';
|
||||
|
||||
export const useLogView = ({
|
||||
logViewId,
|
||||
logViews,
|
||||
fetch,
|
||||
}: {
|
||||
logViewId: string;
|
||||
logViews: ILogViewsClient;
|
||||
fetch: HttpHandler;
|
||||
}) => {
|
||||
const [logView, setLogView] = useState<LogView | undefined>(undefined);
|
||||
|
||||
const [resolvedLogView, setResolvedLogView] = useState<ResolvedLogView | undefined>(undefined);
|
||||
|
||||
const [logViewStatus, setLogViewStatus] = useState<LogViewStatus | undefined>(undefined);
|
||||
|
||||
const [loadLogViewRequest, loadLogView] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: logViews.getLogView.bind(logViews),
|
||||
onResolve: setLogView,
|
||||
},
|
||||
[logViews]
|
||||
);
|
||||
|
||||
const [resolveLogViewRequest, resolveLogView] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: logViews.resolveLogView.bind(logViews),
|
||||
onResolve: setResolvedLogView,
|
||||
},
|
||||
[logViews]
|
||||
);
|
||||
|
||||
const [updateLogViewRequest, updateLogView] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: logViews.putLogView.bind(logViews),
|
||||
onResolve: setLogView,
|
||||
},
|
||||
[logViews]
|
||||
);
|
||||
|
||||
const [loadLogViewStatusRequest, loadLogViewStatus] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: logViews.getResolvedLogViewStatus.bind(logViews),
|
||||
onResolve: setLogViewStatus,
|
||||
},
|
||||
[logViews]
|
||||
);
|
||||
|
||||
const derivedDataView = useMemo(
|
||||
() => ({
|
||||
fields: resolvedLogView?.fields ?? [],
|
||||
title: resolvedLogView?.indices ?? 'unknown',
|
||||
}),
|
||||
[resolvedLogView]
|
||||
);
|
||||
|
||||
const isLoadingLogView = loadLogViewRequest.state === 'pending';
|
||||
const isResolvingLogView = resolveLogViewRequest.state === 'pending';
|
||||
const isLoadingLogViewStatus = loadLogViewStatusRequest.state === 'pending';
|
||||
const isUpdatingLogView = updateLogViewRequest.state === 'pending';
|
||||
|
||||
const isLoading =
|
||||
isLoadingLogView || isResolvingLogView || isLoadingLogViewStatus || isUpdatingLogView;
|
||||
|
||||
const isUninitialized = loadLogViewRequest.state === 'uninitialized';
|
||||
|
||||
const hasFailedLoadingLogView = loadLogViewRequest.state === 'rejected';
|
||||
const hasFailedResolvingLogView = resolveLogViewRequest.state === 'rejected';
|
||||
const hasFailedLoadingLogViewStatus = loadLogViewStatusRequest.state === 'rejected';
|
||||
|
||||
const latestLoadLogViewFailures = [
|
||||
loadLogViewRequest,
|
||||
resolveLogViewRequest,
|
||||
loadLogViewStatusRequest,
|
||||
]
|
||||
.filter(isRejectedPromiseState)
|
||||
.map(({ value }) => (value instanceof Error ? value : new Error(`${value}`)));
|
||||
|
||||
const hasFailedLoading = latestLoadLogViewFailures.length > 0;
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const loadedLogView = await loadLogView(logViewId);
|
||||
const resolvedLoadedLogView = await resolveLogView(loadedLogView.attributes);
|
||||
const resolvedLogViewStatus = await loadLogViewStatus(resolvedLoadedLogView);
|
||||
|
||||
return [loadedLogView, resolvedLoadedLogView, resolvedLogViewStatus];
|
||||
}, [logViewId, loadLogView, loadLogViewStatus, resolveLogView]);
|
||||
|
||||
const update = useCallback(
|
||||
async (logViewAttributes: Partial<LogViewAttributes>) => {
|
||||
const updatedLogView = await updateLogView(logViewId, logViewAttributes);
|
||||
const resolvedUpdatedLogView = await resolveLogView(updatedLogView.attributes);
|
||||
const resolvedLogViewStatus = await loadLogViewStatus(resolvedUpdatedLogView);
|
||||
|
||||
return [updatedLogView, resolvedUpdatedLogView, resolvedLogViewStatus];
|
||||
},
|
||||
[logViewId, loadLogViewStatus, resolveLogView, updateLogView]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
return {
|
||||
logViewId,
|
||||
isUninitialized,
|
||||
derivedDataView,
|
||||
|
||||
// Failure states
|
||||
hasFailedLoading,
|
||||
hasFailedLoadingLogView,
|
||||
hasFailedLoadingLogViewStatus,
|
||||
hasFailedResolvingLogView,
|
||||
latestLoadLogViewFailures,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isLoadingLogView,
|
||||
isLoadingLogViewStatus,
|
||||
isResolvingLogView,
|
||||
|
||||
// data
|
||||
logView,
|
||||
resolvedLogView,
|
||||
logViewStatus,
|
||||
|
||||
// actions
|
||||
load,
|
||||
update,
|
||||
};
|
||||
};
|
||||
|
||||
export const [LogViewProvider, useLogViewContext] = createContainer(useLogView);
|
|
@ -5,21 +5,24 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { createMetricsHasData, createMetricsFetchData } from './metrics_overview_fetchers';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from './types';
|
||||
import moment from 'moment';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { createMetricsFetchData, createMetricsHasData } from './metrics_overview_fetchers';
|
||||
import { createInfraPluginStartMock } from './mocks';
|
||||
import { FAKE_OVERVIEW_RESPONSE } from './test_utils';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from './types';
|
||||
|
||||
function setup() {
|
||||
const core = coreMock.createStart();
|
||||
const pluginStart = createInfraPluginStartMock();
|
||||
|
||||
const mockedGetStartServices = jest.fn(() => {
|
||||
const deps = {};
|
||||
return Promise.resolve([
|
||||
core as CoreStart,
|
||||
deps as InfraClientStartDeps,
|
||||
{} as InfraClientStartExports,
|
||||
pluginStart,
|
||||
]) as Promise<[CoreStart, InfraClientStartDeps, InfraClientStartExports]>;
|
||||
});
|
||||
return { core, mockedGetStartServices };
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
import { FetchDataParams, MetricsFetchDataResponse } from '../../observability/public';
|
||||
import { TopNodesRequest, TopNodesResponse } from '../common/http_api/overview_api';
|
||||
import { InfraClientCoreSetup } from './types';
|
||||
import { InfraStaticSourceConfiguration } from '../common/source_configuration/source_configuration';
|
||||
import { InfraClientStartServicesAccessor } from './types';
|
||||
|
||||
export const createMetricsHasData =
|
||||
(getStartServices: InfraClientCoreSetup['getStartServices']) => async () => {
|
||||
(getStartServices: InfraClientStartServicesAccessor) => async () => {
|
||||
const [coreServices] = await getStartServices();
|
||||
const { http } = coreServices;
|
||||
const results = await http.get<{
|
||||
|
@ -30,7 +30,7 @@ export const createMetricsHasData =
|
|||
};
|
||||
|
||||
export const createMetricsFetchData =
|
||||
(getStartServices: InfraClientCoreSetup['getStartServices']) =>
|
||||
(getStartServices: InfraClientStartServicesAccessor) =>
|
||||
async ({ absoluteTime, intervalString }: FetchDataParams): Promise<MetricsFetchDataResponse> => {
|
||||
const [coreServices] = await getStartServices();
|
||||
const { http } = coreServices;
|
||||
|
|
19
x-pack/plugins/infra/public/mocks.tsx
Normal file
19
x-pack/plugins/infra/public/mocks.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock';
|
||||
import { InfraClientStartExports } from './types';
|
||||
|
||||
export const createInfraPluginStartMock = () => ({
|
||||
logViews: createLogViewsServiceStartMock(),
|
||||
ContainerMetricsTable: () => <div />,
|
||||
HostMetricsTable: () => <div />,
|
||||
PodMetricsTable: () => <div />,
|
||||
});
|
||||
|
||||
export const _ensureTypeCompatibility = (): InfraClientStartExports => createInfraPluginStartMock();
|
|
@ -11,22 +11,22 @@ import React from 'react';
|
|||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { httpServiceMock } from 'src/core/public/mocks';
|
||||
import { KibanaContextProvider, KibanaPageTemplate } from 'src/plugins/kibana_react/public';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import { useLogView } from '../../hooks/use_log_view';
|
||||
import {
|
||||
createLoadedUseLogSourceMock,
|
||||
createLoadingUseLogSourceMock,
|
||||
} from '../../containers/logs/log_source/log_source.mock';
|
||||
createLoadedUseLogViewMock,
|
||||
createLoadingUseLogViewMock,
|
||||
} from '../../hooks/use_log_view.mock';
|
||||
import { LinkToLogsPage } from './link_to_logs';
|
||||
|
||||
jest.mock('../../containers/logs/log_source');
|
||||
const useLogSourceMock = useLogSource as jest.MockedFunction<typeof useLogSource>;
|
||||
jest.mock('../../hooks/use_log_view');
|
||||
const useLogViewMock = useLogView as jest.MockedFunction<typeof useLogView>;
|
||||
|
||||
const renderRoutes = (routes: React.ReactElement) => {
|
||||
const history = createMemoryHistory();
|
||||
const services = {
|
||||
http: httpServiceMock.createStartContract(),
|
||||
data: {
|
||||
indexPatterns: {},
|
||||
logViews: {
|
||||
client: {},
|
||||
},
|
||||
observability: {
|
||||
navigation: {
|
||||
|
@ -48,12 +48,12 @@ const renderRoutes = (routes: React.ReactElement) => {
|
|||
};
|
||||
|
||||
describe('LinkToLogsPage component', () => {
|
||||
beforeEach(() => {
|
||||
useLogSourceMock.mockImplementation(createLoadedUseLogSourceMock());
|
||||
beforeEach(async () => {
|
||||
useLogViewMock.mockImplementation(await createLoadedUseLogViewMock());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLogSourceMock.mockRestore();
|
||||
useLogViewMock.mockRestore();
|
||||
});
|
||||
|
||||
describe('default route', () => {
|
||||
|
@ -199,7 +199,7 @@ describe('LinkToLogsPage component', () => {
|
|||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', async () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
useLogViewMock.mockImplementation(createLoadingUseLogViewMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
|
@ -209,7 +209,7 @@ describe('LinkToLogsPage component', () => {
|
|||
|
||||
history.push('/link-to/host-logs/HOST_NAME');
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('nodeLoadingPage-host')).not.toBeEmpty();
|
||||
expect(queryByTestId('nodeLoadingPage-host')).not.toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -258,7 +258,7 @@ describe('LinkToLogsPage component', () => {
|
|||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
useLogViewMock.mockImplementation(createLoadingUseLogViewMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
|
@ -268,7 +268,7 @@ describe('LinkToLogsPage component', () => {
|
|||
|
||||
history.push('/link-to/container-logs/CONTAINER_ID');
|
||||
|
||||
expect(queryByTestId('nodeLoadingPage-container')).not.toBeEmpty();
|
||||
expect(queryByTestId('nodeLoadingPage-container')).not.toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -314,7 +314,7 @@ describe('LinkToLogsPage component', () => {
|
|||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
useLogViewMock.mockImplementation(createLoadingUseLogViewMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
|
@ -324,7 +324,7 @@ describe('LinkToLogsPage component', () => {
|
|||
|
||||
history.push('/link-to/pod-logs/POD_UID');
|
||||
|
||||
expect(queryByTestId('nodeLoadingPage-pod')).not.toBeEmpty();
|
||||
expect(queryByTestId('nodeLoadingPage-pod')).not.toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,20 +6,20 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { flowRight } from 'lodash';
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { flowRight } from 'lodash';
|
||||
import { LinkDescriptor } from '../../../../observability/public';
|
||||
import { findInventoryFields } from '../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
|
||||
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { LinkDescriptor } from '../../../../observability/public';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
|
||||
import { useLogView } from '../../hooks/use_log_view';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
|
||||
type RedirectToNodeLogsType = RouteComponentProps<{
|
||||
nodeId: string;
|
||||
|
@ -34,14 +34,14 @@ export const RedirectToNodeLogs = ({
|
|||
location,
|
||||
}: RedirectToNodeLogsType) => {
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
const { isLoading, loadSource } = useLogSource({
|
||||
const { isLoading, load } = useLogView({
|
||||
fetch: services.http.fetch,
|
||||
sourceId,
|
||||
indexPatternsService: services.data.indexPatterns,
|
||||
logViewId: sourceId,
|
||||
logViews: services.logViews.client,
|
||||
});
|
||||
|
||||
useMount(() => {
|
||||
loadSource();
|
||||
load();
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { LoadingPage } from '../../../components/loading_page';
|
||||
import {
|
||||
|
@ -21,11 +22,10 @@ import {
|
|||
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { LogEntryCategoriesResultsContent } from './page_results_content';
|
||||
import { LogEntryCategoriesSetupContent } from './page_setup_content';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
|
||||
const logCategoriesTitle = i18n.translate('xpack.infra.logs.logCategoriesTitle', {
|
||||
defaultMessage: 'Categories',
|
||||
|
@ -115,10 +115,10 @@ const CategoriesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
|
|||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { logViewStatus } = useLogViewContext();
|
||||
return (
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
hasData={logViewStatus?.index !== 'missing'}
|
||||
data-test-subj="logsLogEntryCategoriesPage"
|
||||
pageHeader={
|
||||
rest.isEmptyState
|
||||
|
|
|
@ -10,19 +10,19 @@ import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging
|
|||
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
|
||||
export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
latestLoadSourceFailures,
|
||||
loadSource,
|
||||
resolvedSourceConfiguration,
|
||||
sourceId,
|
||||
} = useLogSourceContext();
|
||||
latestLoadLogViewFailures,
|
||||
load,
|
||||
resolvedLogView,
|
||||
logViewId,
|
||||
} = useLogViewContext();
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
|
@ -31,17 +31,17 @@ export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ child
|
|||
if (space == null) {
|
||||
return null;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
return <LogSourceErrorPage errors={latestLoadLogViewFailures} onRetry={load} />;
|
||||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (resolvedSourceConfiguration != null) {
|
||||
} else if (resolvedLogView != null) {
|
||||
return (
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
|
|
|
@ -11,13 +11,21 @@ import { i18n } from '@kbn/i18n';
|
|||
import moment from 'moment';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import useInterval from 'react-use/lib/useInterval';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { MLJobsAwaitingNodeWarning, ML_PAGES, useMlHref } from '../../../../../ml/public';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { TimeRange } from '../../../../common/time/time_range';
|
||||
import { CategoryJobNoticesSection } from '../../../components/logging/log_analysis_job_status';
|
||||
import { AnalyzeInMlButton } from '../../../components/logging/log_analysis_results';
|
||||
import { DatasetsSelector } from '../../../components/logging/log_analysis_results/datasets_selector';
|
||||
import { RecreateJobButton } from '../../../components/logging/log_analysis_setup/create_job_button';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis/log_analysis_capabilities';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { PageViewLogInContext } from '../stream/page_view_log_in_context';
|
||||
import { TopCategoriesSection } from './sections/top_categories';
|
||||
import { useLogEntryCategoriesResults } from './use_log_entry_categories_results';
|
||||
|
@ -25,15 +33,6 @@ import {
|
|||
StringTimeRange,
|
||||
useLogEntryCategoriesResultsUrlState,
|
||||
} from './use_log_entry_categories_results_url_state';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis/log_analysis_capabilities';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { RecreateJobButton } from '../../../components/logging/log_analysis_setup/create_job_button';
|
||||
import { AnalyzeInMlButton } from '../../../components/logging/log_analysis_results';
|
||||
import { useMlHref, ML_PAGES } from '../../../../../ml/public';
|
||||
import { DatasetsSelector } from '../../../components/logging/log_analysis_results/datasets_selector';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { MLJobsAwaitingNodeWarning } from '../../../../../ml/public';
|
||||
|
||||
const JOB_STATUS_POLLING_INTERVAL = 30000;
|
||||
|
||||
|
@ -52,7 +51,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
services: { ml, http },
|
||||
} = useKibanaContextForPlugin();
|
||||
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { logViewStatus } = useLogViewContext();
|
||||
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
|
||||
|
||||
const {
|
||||
|
@ -212,7 +211,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
endTimestamp={categoryQueryTimeRange.timeRange.endTime}
|
||||
>
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
hasData={logViewStatus?.index !== 'missing'}
|
||||
pageHeader={{
|
||||
pageTitle,
|
||||
rightSideItems: [
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import useInterval from 'react-use/lib/useInterval';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { LoadingPage } from '../../../components/loading_page';
|
||||
import {
|
||||
|
@ -20,14 +21,13 @@ import {
|
|||
useLogAnalysisSetupFlyoutStateContext,
|
||||
} from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { LogEntryRateResultsContent } from './page_results_content';
|
||||
import { LogEntryRateSetupContent } from './page_setup_content';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
|
||||
|
||||
const JOB_STATUS_POLLING_INTERVAL = 30000;
|
||||
|
||||
|
@ -156,10 +156,10 @@ const AnomaliesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
|
|||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { logViewStatus } = useLogViewContext();
|
||||
return (
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
hasData={logViewStatus?.index !== 'missing'}
|
||||
data-test-subj="logsLogEntryRatePage"
|
||||
pageHeader={
|
||||
rest.isEmptyState
|
||||
|
|
|
@ -12,19 +12,19 @@ import { SourceLoadingPage } from '../../../components/source_loading_page';
|
|||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { LogEntryRateModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
|
||||
export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
latestLoadSourceFailures,
|
||||
loadSource,
|
||||
resolvedSourceConfiguration,
|
||||
sourceId,
|
||||
} = useLogSourceContext();
|
||||
latestLoadLogViewFailures,
|
||||
load,
|
||||
logViewId,
|
||||
resolvedLogView,
|
||||
} = useLogViewContext();
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
|
@ -35,23 +35,23 @@ export const LogEntryRatePageProviders: React.FunctionComponent = ({ children })
|
|||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
} else if (resolvedSourceConfiguration != null) {
|
||||
return <LogSourceErrorPage errors={latestLoadLogViewFailures} onRetry={load} />;
|
||||
} else if (resolvedLogView != null) {
|
||||
return (
|
||||
<LogFlyout.Provider>
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
>
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
|
|
|
@ -6,34 +6,34 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker } from '@elastic/eui';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import moment from 'moment';
|
||||
import { stringify } from 'query-string';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { encode, RisonValue } from 'rison-node';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { MLJobsAwaitingNodeWarning } from '../../../../../ml/public';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
import {
|
||||
CategoryJobNoticesSection,
|
||||
LogAnalysisJobProblemIndicator,
|
||||
} from '../../../components/logging/log_analysis_job_status';
|
||||
import { DatasetsSelector } from '../../../components/logging/log_analysis_results/datasets_selector';
|
||||
import { ManageJobsButton } from '../../../components/logging/log_analysis_setup/manage_jobs_button';
|
||||
import { useLogAnalysisSetupFlyoutStateContext } from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { LogEntryFlyout } from '../../../components/logging/log_entry_flyout';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis/log_analysis_capabilities';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { useLogEntryFlyoutContext } from '../../../containers/logs/log_flyout';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { AnomaliesResults } from './sections/anomalies';
|
||||
import { useDatasetFiltering } from './use_dataset_filtering';
|
||||
import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results';
|
||||
import { useLogAnalysisResultsUrlState } from './use_log_entry_rate_results_url_state';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { ManageJobsButton } from '../../../components/logging/log_analysis_setup/manage_jobs_button';
|
||||
import { MLJobsAwaitingNodeWarning } from '../../../../../ml/public';
|
||||
|
||||
export const SORT_DEFAULTS = {
|
||||
direction: 'desc' as const,
|
||||
|
@ -52,7 +52,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
|
||||
const navigateToApp = useKibana().services.application?.navigateToApp;
|
||||
|
||||
const { sourceId, sourceStatus } = useLogSourceContext();
|
||||
const { logViewId, logViewStatus } = useLogViewContext();
|
||||
|
||||
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
|
||||
|
||||
|
@ -142,7 +142,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
datasets,
|
||||
isLoadingDatasets,
|
||||
} = useLogEntryAnomaliesResults({
|
||||
sourceId,
|
||||
sourceId: logViewId,
|
||||
startTime: timeRange.value.startTime,
|
||||
endTime: timeRange.value.endTime,
|
||||
defaultSortOptions: SORT_DEFAULTS,
|
||||
|
@ -196,7 +196,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
|
||||
return (
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
hasData={logViewStatus?.index !== 'missing'}
|
||||
pageHeader={{
|
||||
pageTitle,
|
||||
rightSideItems: [<ManageJobsButton onClick={showModuleList} size="s" />],
|
||||
|
@ -272,7 +272,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
logEntryId={flyoutLogEntryId}
|
||||
onCloseFlyout={closeLogEntryFlyout}
|
||||
onSetFieldFilter={linkToLogStream}
|
||||
sourceId={sourceId}
|
||||
sourceId={logViewId}
|
||||
/>
|
||||
) : null}
|
||||
</LogsPageTemplate>
|
||||
|
|
|
@ -11,10 +11,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
import { LogEntryAnomaly, isCategoryAnomaly } from '../../../../../../common/log_analysis';
|
||||
import { isCategoryAnomaly, LogEntryAnomaly } from '../../../../../../common/log_analysis';
|
||||
import { TimeRange } from '../../../../../../common/time/time_range';
|
||||
import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples';
|
||||
import { useLogSourceContext } from '../../../../../containers/logs/log_source';
|
||||
import { useLogViewContext } from '../../../../../hooks/use_log_view';
|
||||
import { useLogEntryExamples } from '../../use_log_entry_examples';
|
||||
import { LogEntryExampleMessage, LogEntryExampleMessageHeaders } from './log_entry_example';
|
||||
|
||||
|
@ -28,7 +28,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{
|
|||
anomaly: LogEntryAnomaly;
|
||||
timeRange: TimeRange;
|
||||
}> = ({ anomaly, timeRange }) => {
|
||||
const { sourceId } = useLogSourceContext();
|
||||
const { logViewId } = useLogViewContext();
|
||||
|
||||
const {
|
||||
getLogEntryExamples,
|
||||
|
@ -39,7 +39,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{
|
|||
dataset: anomaly.dataset,
|
||||
endTime: anomaly.startTime + anomaly.duration,
|
||||
exampleCount: EXAMPLE_COUNT,
|
||||
sourceId,
|
||||
sourceId: logViewId,
|
||||
startTime: anomaly.startTime,
|
||||
categoryId: isCategoryAnomaly(anomaly) ? anomaly.categoryId : undefined,
|
||||
});
|
||||
|
|
|
@ -5,41 +5,31 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui';
|
||||
import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
|
||||
import { AlertDropdown } from '../../alerting/log_threshold';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { HeaderMenuPortal, useLinkProps } from '../../../../observability/public';
|
||||
import { AlertDropdown } from '../../alerting/log_threshold';
|
||||
import { DocumentTitle } from '../../components/document_title';
|
||||
import { HelpCenterContent } from '../../components/help_center_content';
|
||||
import { useLogSourceContext } from '../../containers/logs/log_source';
|
||||
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
|
||||
import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider';
|
||||
import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params';
|
||||
import { LogEntryCategoriesPage } from './log_entry_categories';
|
||||
import { LogEntryRatePage } from './log_entry_rate';
|
||||
import { LogsSettingsPage } from './settings';
|
||||
import { StreamPage } from './stream';
|
||||
import { HeaderMenuPortal } from '../../../../observability/public';
|
||||
import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider';
|
||||
import { useLinkProps } from '../../../../observability/public';
|
||||
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
|
||||
|
||||
export const LogsPageContent: React.FunctionComponent = () => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext);
|
||||
|
||||
const { initialize } = useLogSourceContext();
|
||||
|
||||
const kibana = useKibana();
|
||||
|
||||
useReadOnlyBadge(!uiCapabilities?.logs?.save);
|
||||
|
||||
useMount(() => {
|
||||
initialize();
|
||||
});
|
||||
|
||||
// !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts
|
||||
const streamTab = {
|
||||
app: 'logs',
|
||||
|
|
|
@ -6,21 +6,21 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
|
||||
import { LogAnalysisCapabilitiesProvider } from '../../containers/logs/log_analysis';
|
||||
import { LogSourceProvider } from '../../containers/logs/log_source';
|
||||
import { useSourceId } from '../../containers/source_id';
|
||||
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
|
||||
import { LogViewProvider } from '../../hooks/use_log_view';
|
||||
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const [sourceId] = useSourceId();
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
return (
|
||||
<LogSourceProvider
|
||||
sourceId={sourceId}
|
||||
<LogViewProvider
|
||||
fetch={services.http.fetch}
|
||||
indexPatternsService={services.data.indexPatterns}
|
||||
logViewId={sourceId}
|
||||
logViews={services.logViews.client}
|
||||
>
|
||||
<LogAnalysisCapabilitiesProvider>{children}</LogAnalysisCapabilitiesProvider>
|
||||
</LogSourceProvider>
|
||||
</LogViewProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiCode, EuiDescribedFormGroup, EuiFieldText, EuiFormRow } from '@elast
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { LogIndexNameReference } from '../../../../common/log_sources';
|
||||
import { LogIndexNameReference } from '../../../../common/log_views';
|
||||
import { FormElement } from './form_elements';
|
||||
import { getFormRowProps, getInputFieldProps } from './form_field_props';
|
||||
import { FormValidationError } from './validation_errors';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { LogIndexPatternReference } from '../../../../common/log_sources';
|
||||
import { LogDataViewReference } from '../../../../common/log_views';
|
||||
import { useLinkProps } from '../../../../../observability/public';
|
||||
import { FormElement } from './form_elements';
|
||||
import { getFormRowProps } from './form_field_props';
|
||||
|
@ -19,7 +19,7 @@ import { FormValidationError } from './validation_errors';
|
|||
export const IndexPatternConfigurationPanel: React.FC<{
|
||||
isLoading: boolean;
|
||||
isReadOnly: boolean;
|
||||
indexPatternFormElement: FormElement<LogIndexPatternReference | undefined, FormValidationError>;
|
||||
indexPatternFormElement: FormElement<LogDataViewReference | undefined, FormValidationError>;
|
||||
}> = ({ isLoading, isReadOnly, indexPatternFormElement }) => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_source_configuration_index_pattern' });
|
||||
useTrackPageview({
|
||||
|
@ -29,11 +29,11 @@ export const IndexPatternConfigurationPanel: React.FC<{
|
|||
});
|
||||
|
||||
const changeIndexPatternId = useCallback(
|
||||
(indexPatternId: string | undefined) => {
|
||||
if (indexPatternId != null) {
|
||||
(dataViewId: string | undefined) => {
|
||||
if (dataViewId != null) {
|
||||
indexPatternFormElement.updateValue(() => ({
|
||||
type: 'index_pattern',
|
||||
indexPatternId,
|
||||
type: 'data_view',
|
||||
dataViewId,
|
||||
}));
|
||||
} else {
|
||||
indexPatternFormElement.updateValue(() => undefined);
|
||||
|
@ -78,7 +78,7 @@ export const IndexPatternConfigurationPanel: React.FC<{
|
|||
<IndexPatternSelector
|
||||
isLoading={isLoading || indexPatternFormElement.validity.validity === 'pending'}
|
||||
isReadOnly={isReadOnly}
|
||||
indexPatternId={indexPatternFormElement.value?.indexPatternId}
|
||||
indexPatternId={indexPatternFormElement.value?.dataViewId}
|
||||
onChangeIndexPatternId={changeIndexPatternId}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { SavedObjectNotFound } from '../../../../../../../src/plugins/kibana_utils/common';
|
||||
import { useUiTracker } from '../../../../../observability/public';
|
||||
import {
|
||||
LogDataViewReference,
|
||||
LogIndexNameReference,
|
||||
logIndexNameReferenceRT,
|
||||
LogIndexPatternReference,
|
||||
} from '../../../../common/log_sources';
|
||||
} from '../../../../common/log_views';
|
||||
import { SavedObjectNotFound } from '../../../../../../../src/plugins/kibana_utils/common';
|
||||
import { useUiTracker } from '../../../../../observability/public';
|
||||
import { useKibanaIndexPatternService } from '../../../hooks/use_kibana_index_patterns';
|
||||
import { useFormElement } from './form_elements';
|
||||
import {
|
||||
|
@ -21,7 +21,7 @@ import {
|
|||
validateStringNotEmpty,
|
||||
} from './validation_errors';
|
||||
|
||||
export type LogIndicesFormState = LogIndexNameReference | LogIndexPatternReference | undefined;
|
||||
export type LogIndicesFormState = LogIndexNameReference | LogDataViewReference | undefined;
|
||||
|
||||
export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => {
|
||||
const indexPatternService = useKibanaIndexPatternService();
|
||||
|
@ -37,23 +37,20 @@ export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => {
|
|||
} else if (logIndexNameReferenceRT.is(logIndices)) {
|
||||
return validateStringNotEmpty('log indices', logIndices.indexName);
|
||||
} else {
|
||||
const emptyStringErrors = validateStringNotEmpty(
|
||||
'log data view',
|
||||
logIndices.indexPatternId
|
||||
);
|
||||
const emptyStringErrors = validateStringNotEmpty('log data view', logIndices.dataViewId);
|
||||
|
||||
if (emptyStringErrors.length > 0) {
|
||||
return emptyStringErrors;
|
||||
}
|
||||
|
||||
const indexPatternErrors = await indexPatternService
|
||||
.get(logIndices.indexPatternId)
|
||||
.get(logIndices.dataViewId)
|
||||
.then(validateIndexPattern, (error): FormValidationError[] => {
|
||||
if (error instanceof SavedObjectNotFound) {
|
||||
return [
|
||||
{
|
||||
type: 'missing_index_pattern' as const,
|
||||
indexPatternId: logIndices.indexPatternId,
|
||||
indexPatternId: logIndices.dataViewId,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
|
|
|
@ -11,10 +11,10 @@ import React, { useCallback } from 'react';
|
|||
import { useUiTracker } from '../../../../../observability/public';
|
||||
import {
|
||||
logIndexNameReferenceRT,
|
||||
LogIndexPatternReference,
|
||||
logIndexPatternReferenceRT,
|
||||
LogDataViewReference,
|
||||
logDataViewReferenceRT,
|
||||
LogIndexReference,
|
||||
} from '../../../../common/log_sources';
|
||||
} from '../../../../common/log_views';
|
||||
import { FormElement, isFormElementForType } from './form_elements';
|
||||
import { IndexNamesConfigurationPanel } from './index_names_configuration_panel';
|
||||
import { IndexPatternConfigurationPanel } from './index_pattern_configuration_panel';
|
||||
|
@ -28,7 +28,7 @@ export const IndicesConfigurationPanel = React.memo<{
|
|||
const trackChangeIndexSourceType = useUiTracker({ app: 'infra_logs' });
|
||||
|
||||
const changeToIndexPatternType = useCallback(() => {
|
||||
if (indicesFormElement.initialValue?.type === 'index_pattern') {
|
||||
if (logDataViewReferenceRT.is(indicesFormElement.initialValue)) {
|
||||
indicesFormElement.updateValue(() => indicesFormElement.initialValue);
|
||||
} else {
|
||||
indicesFormElement.updateValue(() => undefined);
|
||||
|
@ -83,11 +83,11 @@ export const IndicesConfigurationPanel = React.memo<{
|
|||
}
|
||||
name="dataView"
|
||||
value="dataView"
|
||||
checked={isIndexPatternFormElement(indicesFormElement)}
|
||||
checked={isDataViewFormElement(indicesFormElement)}
|
||||
onChange={changeToIndexPatternType}
|
||||
disabled={isReadOnly}
|
||||
>
|
||||
{isIndexPatternFormElement(indicesFormElement) && (
|
||||
{isDataViewFormElement(indicesFormElement) && (
|
||||
<IndexPatternConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
isReadOnly={isReadOnly}
|
||||
|
@ -127,9 +127,9 @@ export const IndicesConfigurationPanel = React.memo<{
|
|||
);
|
||||
});
|
||||
|
||||
const isIndexPatternFormElement = isFormElementForType(
|
||||
(value): value is LogIndexPatternReference | undefined =>
|
||||
value == null || logIndexPatternReferenceRT.is(value)
|
||||
const isDataViewFormElement = isFormElementForType(
|
||||
(value): value is LogDataViewReference | undefined =>
|
||||
value == null || logDataViewReferenceRT.is(value)
|
||||
);
|
||||
|
||||
const isIndexNamesFormElement = isFormElementForType(logIndexNameReferenceRT.is);
|
||||
|
|
|
@ -6,30 +6,28 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { LogSourceConfigurationProperties } from '../../../containers/logs/log_source';
|
||||
import { LogViewAttributes } from '../../../../common/log_views';
|
||||
import { useCompositeFormElement } from './form_elements';
|
||||
import { useLogIndicesFormElement } from './indices_configuration_form_state';
|
||||
import { useLogColumnsFormElement } from './log_columns_configuration_form_state';
|
||||
import { useNameFormElement } from './name_configuration_form_state';
|
||||
|
||||
export const useLogSourceConfigurationFormState = (
|
||||
configuration?: LogSourceConfigurationProperties
|
||||
) => {
|
||||
const nameFormElement = useNameFormElement(configuration?.name ?? '');
|
||||
export const useLogSourceConfigurationFormState = (logViewAttributes?: LogViewAttributes) => {
|
||||
const nameFormElement = useNameFormElement(logViewAttributes?.name ?? '');
|
||||
|
||||
const logIndicesFormElement = useLogIndicesFormElement(
|
||||
useMemo(
|
||||
() =>
|
||||
configuration?.logIndices ?? {
|
||||
logViewAttributes?.logIndices ?? {
|
||||
type: 'index_name',
|
||||
indexName: '',
|
||||
},
|
||||
[configuration]
|
||||
[logViewAttributes]
|
||||
)
|
||||
);
|
||||
|
||||
const logColumnsFormElement = useLogColumnsFormElement(
|
||||
useMemo(() => configuration?.logColumns ?? [], [configuration])
|
||||
useMemo(() => logViewAttributes?.logColumns ?? [], [logViewAttributes])
|
||||
);
|
||||
|
||||
const sourceConfigurationFormElement = useCompositeFormElement(
|
||||
|
|
|
@ -17,18 +17,17 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
|
||||
import { Prompt, useTrackPageview } from '../../../../../observability/public';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { Prompt } from '../../../../../observability/public';
|
||||
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { settingsTitle } from '../../../translations';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { IndicesConfigurationPanel } from './indices_configuration_panel';
|
||||
import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel';
|
||||
import { NameConfigurationPanel } from './name_configuration_panel';
|
||||
import { LogSourceConfigurationFormErrors } from './source_configuration_form_errors';
|
||||
import { useLogSourceConfigurationFormState } from './source_configuration_form_state';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { settingsTitle } from '../../../translations';
|
||||
|
||||
export const LogsSettingsPage = () => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
|
@ -47,18 +46,12 @@ export const LogsSettingsPage = () => {
|
|||
},
|
||||
]);
|
||||
|
||||
const {
|
||||
sourceConfiguration: source,
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
updateSource,
|
||||
resolvedSourceConfiguration,
|
||||
} = useLogSourceContext();
|
||||
const { logView, hasFailedLoadingLogView, isLoading, isUninitialized, update, resolvedLogView } =
|
||||
useLogViewContext();
|
||||
|
||||
const availableFields = useMemo(
|
||||
() => resolvedSourceConfiguration?.fields.map((field) => field.name) ?? [],
|
||||
[resolvedSourceConfiguration]
|
||||
() => resolvedLogView?.fields.map((field) => field.name) ?? [],
|
||||
[resolvedLogView]
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -67,22 +60,22 @@ export const LogsSettingsPage = () => {
|
|||
logIndicesFormElement,
|
||||
logColumnsFormElement,
|
||||
nameFormElement,
|
||||
} = useLogSourceConfigurationFormState(source?.configuration);
|
||||
} = useLogSourceConfigurationFormState(logView?.attributes);
|
||||
|
||||
const persistUpdates = useCallback(async () => {
|
||||
await updateSource(formState);
|
||||
await update(formState);
|
||||
sourceConfigurationFormElement.resetValue();
|
||||
}, [updateSource, sourceConfigurationFormElement, formState]);
|
||||
}, [update, sourceConfigurationFormElement, formState]);
|
||||
|
||||
const isWriteable = useMemo(
|
||||
() => shouldAllowEdit && source && source.origin !== 'internal',
|
||||
[shouldAllowEdit, source]
|
||||
() => shouldAllowEdit && logView && logView.origin !== 'internal',
|
||||
[shouldAllowEdit, logView]
|
||||
);
|
||||
|
||||
if ((isLoading || isUninitialized) && !resolvedSourceConfiguration) {
|
||||
if ((isLoading || isUninitialized) && !resolvedLogView) {
|
||||
return <SourceLoadingPage />;
|
||||
}
|
||||
if (hasFailedLoadingSource) {
|
||||
if (hasFailedLoadingLogView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { LogsPageLogsContent } from './page_logs_content';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { LogsPageTemplate } from '../page_template';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public';
|
||||
import { LogsPageLogsContent } from './page_logs_content';
|
||||
|
||||
const streamTitle = i18n.translate('xpack.infra.logs.streamPageTitle', {
|
||||
defaultMessage: 'Stream',
|
||||
|
@ -24,20 +24,20 @@ export const StreamPageContent: React.FunctionComponent = () => {
|
|||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
loadSource,
|
||||
latestLoadSourceFailures,
|
||||
sourceStatus,
|
||||
} = useLogSourceContext();
|
||||
latestLoadLogViewFailures,
|
||||
load,
|
||||
logViewStatus,
|
||||
} = useLogViewContext();
|
||||
|
||||
if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
return <LogSourceErrorPage errors={latestLoadLogViewFailures} onRetry={load} />;
|
||||
} else {
|
||||
return (
|
||||
<LogStreamPageWrapper className={APP_WRAPPER_CLASS}>
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
hasData={logViewStatus?.index !== 'missing'}
|
||||
pageHeader={{
|
||||
pageTitle: streamTitle,
|
||||
}}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { useContext, useCallback, useMemo, useEffect } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { LogEntry } from '../../../../common/log_entry';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
|
@ -25,12 +25,12 @@ import {
|
|||
} from '../../../containers/logs/log_flyout';
|
||||
import { LogHighlightsState } from '../../../containers/logs/log_highlights';
|
||||
import { LogPositionState } from '../../../containers/logs/log_position';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useLogStreamContext } from '../../../containers/logs/log_stream';
|
||||
import { WithSummary } from '../../../containers/logs/log_summary';
|
||||
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
|
||||
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
|
||||
import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { datemathToEpochMillis, isValidDatemath } from '../../../utils/datemath';
|
||||
import { LogsToolbar } from './page_toolbar';
|
||||
import { PageViewLogInContext } from './page_view_log_in_context';
|
||||
|
@ -38,7 +38,7 @@ import { PageViewLogInContext } from './page_view_log_in_context';
|
|||
const PAGE_THRESHOLD = 2;
|
||||
|
||||
export const LogsPageLogsContent: React.FunctionComponent = () => {
|
||||
const { resolvedSourceConfiguration, sourceConfiguration, sourceId } = useLogSourceContext();
|
||||
const { resolvedLogView, logView, logViewId } = useLogViewContext();
|
||||
const { textScale, textWrap } = useContext(LogViewConfiguration.Context);
|
||||
const {
|
||||
surroundingLogsId,
|
||||
|
@ -216,14 +216,12 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
logEntryId={flyoutLogEntryId}
|
||||
onCloseFlyout={closeLogEntryFlyout}
|
||||
onSetFieldFilter={setFilter}
|
||||
sourceId={sourceId}
|
||||
sourceId={logViewId}
|
||||
/>
|
||||
) : null}
|
||||
<PageContent key={`${sourceId}-${sourceConfiguration?.version}`}>
|
||||
<PageContent key={`${logViewId}-${logView?.version}`}>
|
||||
<ScrollableLogTextStreamView
|
||||
columnConfigurations={
|
||||
(resolvedSourceConfiguration && resolvedSourceConfiguration.columns) || []
|
||||
}
|
||||
columnConfigurations={(resolvedLogView && resolvedLogView.columns) || []}
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
hasMoreBeforeStart={hasMoreBeforeStart}
|
||||
isLoadingMore={isLoadingMore}
|
||||
|
|
|
@ -6,20 +6,20 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/log_filter';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
|
||||
import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights';
|
||||
import { LogPositionState, WithLogPositionUrlState } from '../../../containers/logs/log_position';
|
||||
import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/log_filter';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
|
||||
import { LogStreamProvider, useLogStreamContext } from '../../../containers/logs/log_stream';
|
||||
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
|
||||
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
|
||||
const LogFilterStateProvider: React.FC = ({ children }) => {
|
||||
const { derivedIndexPattern } = useLogSourceContext();
|
||||
const { derivedDataView } = useLogViewContext();
|
||||
|
||||
return (
|
||||
<LogFilterState.Provider indexPattern={derivedIndexPattern}>
|
||||
<LogFilterState.Provider indexPattern={derivedDataView}>
|
||||
<WithLogFilterUrlState />
|
||||
{children}
|
||||
</LogFilterState.Provider>
|
||||
|
@ -28,7 +28,7 @@ const LogFilterStateProvider: React.FC = ({ children }) => {
|
|||
|
||||
const ViewLogInContextProvider: React.FC = ({ children }) => {
|
||||
const { startTimestamp, endTimestamp } = useContext(LogPositionState.Context);
|
||||
const { sourceId } = useLogSourceContext();
|
||||
const { logViewId } = useLogViewContext();
|
||||
|
||||
if (!startTimestamp || !endTimestamp) {
|
||||
return null;
|
||||
|
@ -38,7 +38,7 @@ const ViewLogInContextProvider: React.FC = ({ children }) => {
|
|||
<ViewLogInContext.Provider
|
||||
startTimestamp={startTimestamp}
|
||||
endTimestamp={endTimestamp}
|
||||
sourceId={sourceId}
|
||||
sourceId={logViewId}
|
||||
>
|
||||
{children}
|
||||
</ViewLogInContext.Provider>
|
||||
|
@ -46,7 +46,7 @@ const ViewLogInContextProvider: React.FC = ({ children }) => {
|
|||
};
|
||||
|
||||
const LogEntriesStateProvider: React.FC = ({ children }) => {
|
||||
const { sourceId } = useLogSourceContext();
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { startTimestamp, endTimestamp, targetPosition, isInitialized } = useContext(
|
||||
LogPositionState.Context
|
||||
);
|
||||
|
@ -65,7 +65,7 @@ const LogEntriesStateProvider: React.FC = ({ children }) => {
|
|||
|
||||
return (
|
||||
<LogStreamProvider
|
||||
sourceId={sourceId}
|
||||
sourceId={logViewId}
|
||||
startTimestamp={startTimestamp}
|
||||
endTimestamp={endTimestamp}
|
||||
query={filterQuery?.parsedQuery}
|
||||
|
@ -77,13 +77,13 @@ const LogEntriesStateProvider: React.FC = ({ children }) => {
|
|||
};
|
||||
|
||||
const LogHighlightsStateProvider: React.FC = ({ children }) => {
|
||||
const { sourceId, sourceConfiguration } = useLogSourceContext();
|
||||
const { logViewId, logView } = useLogViewContext();
|
||||
const { topCursor, bottomCursor, entries } = useLogStreamContext();
|
||||
const { filterQuery } = useContext(LogFilterState.Context);
|
||||
|
||||
const highlightsProps = {
|
||||
sourceId,
|
||||
sourceVersion: sourceConfiguration?.version,
|
||||
sourceId: logViewId,
|
||||
sourceVersion: logView?.version,
|
||||
entriesStart: topCursor,
|
||||
entriesEnd: bottomCursor,
|
||||
centerCursor: entries.length > 0 ? entries[Math.floor(entries.length / 2)].cursor : null,
|
||||
|
@ -94,10 +94,10 @@ const LogHighlightsStateProvider: React.FC = ({ children }) => {
|
|||
};
|
||||
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { logViewStatus } = useLogViewContext();
|
||||
|
||||
// The providers assume the source is loaded, so short-circuit them otherwise
|
||||
if (sourceStatus?.logIndexStatus === 'missing') {
|
||||
if (logViewStatus?.index === 'missing') {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { QueryStringInput } from '../../../../../../../src/plugins/data/public';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { LogCustomizationMenu } from '../../../components/logging/log_customization_menu';
|
||||
import { LogDatepicker } from '../../../components/logging/log_datepicker';
|
||||
import { LogHighlightsMenu } from '../../../components/logging/log_highlights_menu';
|
||||
|
@ -19,12 +20,11 @@ import { LogFilterState } from '../../../containers/logs/log_filter';
|
|||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights';
|
||||
import { LogPositionState } from '../../../containers/logs/log_position';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
|
||||
export const LogsToolbar = () => {
|
||||
const { derivedIndexPattern } = useLogSourceContext();
|
||||
const { derivedDataView } = useLogViewContext();
|
||||
const { availableTextScales, setTextScale, setTextWrap, textScale, textWrap } = useContext(
|
||||
LogViewConfiguration.Context
|
||||
);
|
||||
|
@ -57,7 +57,7 @@ export const LogsToolbar = () => {
|
|||
<QueryStringInput
|
||||
disableLanguageSwitcher={true}
|
||||
iconType="search"
|
||||
indexPatterns={[derivedIndexPattern]}
|
||||
indexPatterns={[derivedDataView]}
|
||||
isInvalid={!isFilterQueryDraftValid}
|
||||
onChange={(query: Query) => {
|
||||
setSurroundingLogsId(null);
|
||||
|
|
|
@ -10,10 +10,11 @@ import { AppMountParameters, PluginInitializerContext } from 'kibana/public';
|
|||
import { from } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
|
||||
import { defaultLogViewsStaticConfig } from '../common/log_views';
|
||||
import { InfraPublicConfig } from '../common/plugin_config_types';
|
||||
import { createInventoryMetricRuleType } from './alerting/inventory';
|
||||
import { createLogThresholdRuleType } from './alerting/log_threshold';
|
||||
import { createMetricThresholdRuleType } from './alerting/metric_threshold';
|
||||
import type { CoreProvidersProps } from './apps/common_providers';
|
||||
import { createLazyContainerMetricsTable } from './components/infrastructure_node_metrics_tables/container/create_lazy_container_metrics_table';
|
||||
import { createLazyHostMetricsTable } from './components/infrastructure_node_metrics_tables/host/create_lazy_host_metrics_table';
|
||||
import { createLazyPodMetricsTable } from './components/infrastructure_node_metrics_tables/pod/create_lazy_pod_metrics_table';
|
||||
|
@ -21,17 +22,29 @@ import { LOG_STREAM_EMBEDDABLE } from './components/log_stream/log_stream_embedd
|
|||
import { LogStreamEmbeddableFactoryDefinition } from './components/log_stream/log_stream_embeddable_factory';
|
||||
import { createMetricsFetchData, createMetricsHasData } from './metrics_overview_fetchers';
|
||||
import { registerFeatures } from './register_feature';
|
||||
import { LogViewsService } from './services/log_views';
|
||||
import {
|
||||
InfraClientCoreSetup,
|
||||
InfraClientCoreStart,
|
||||
InfraClientPluginClass,
|
||||
InfraClientSetupDeps,
|
||||
InfraClientStartDeps,
|
||||
InfraClientStartExports,
|
||||
InfraClientStartServices,
|
||||
} from './types';
|
||||
import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './utils/logs_overview_fetchers';
|
||||
|
||||
export class Plugin implements InfraClientPluginClass {
|
||||
constructor(_context: PluginInitializerContext) {}
|
||||
public config: InfraPublicConfig;
|
||||
private logViews: LogViewsService;
|
||||
|
||||
constructor(context: PluginInitializerContext<InfraPublicConfig>) {
|
||||
this.config = context.config.get();
|
||||
this.logViews = new LogViewsService({
|
||||
messageFields:
|
||||
this.config.sources?.default?.fields?.message ?? defaultLogViewsStaticConfig.messageFields,
|
||||
});
|
||||
}
|
||||
|
||||
setup(core: InfraClientCoreSetup, pluginsSetup: InfraClientSetupDeps) {
|
||||
if (pluginsSetup.home) {
|
||||
|
@ -42,7 +55,9 @@ export class Plugin implements InfraClientPluginClass {
|
|||
createInventoryMetricRuleType()
|
||||
);
|
||||
|
||||
pluginsSetup.observability.observabilityRuleTypeRegistry.register(createLogThresholdRuleType());
|
||||
pluginsSetup.observability.observabilityRuleTypeRegistry.register(
|
||||
createLogThresholdRuleType(core)
|
||||
);
|
||||
pluginsSetup.observability.observabilityRuleTypeRegistry.register(
|
||||
createMetricThresholdRuleType()
|
||||
);
|
||||
|
@ -144,10 +159,10 @@ export class Plugin implements InfraClientPluginClass {
|
|||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
// mount callback should not use setup dependencies, get start dependencies instead
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
const [coreStart, pluginsStart, pluginStart] = await core.getStartServices();
|
||||
const { renderApp } = await import('./apps/logs_app');
|
||||
|
||||
return renderApp(coreStart, pluginsStart, params);
|
||||
return renderApp(coreStart, pluginsStart, pluginStart, params);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -186,10 +201,10 @@ export class Plugin implements InfraClientPluginClass {
|
|||
],
|
||||
mount: async (params: AppMountParameters) => {
|
||||
// mount callback should not use setup dependencies, get start dependencies instead
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
const [coreStart, pluginsStart, pluginStart] = await core.getStartServices();
|
||||
const { renderApp } = await import('./apps/metrics_app');
|
||||
|
||||
return renderApp(coreStart, pluginsStart, params);
|
||||
return renderApp(coreStart, pluginsStart, pluginStart, params);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -209,17 +224,22 @@ export class Plugin implements InfraClientPluginClass {
|
|||
}
|
||||
|
||||
start(core: InfraClientCoreStart, plugins: InfraClientStartDeps) {
|
||||
const coreProvidersProps: CoreProvidersProps = {
|
||||
core,
|
||||
plugins,
|
||||
theme$: core.theme.theme$,
|
||||
const getStartServices = (): InfraClientStartServices => [core, plugins, startContract];
|
||||
|
||||
const logViews = this.logViews.start({
|
||||
http: core.http,
|
||||
dataViews: plugins.dataViews,
|
||||
search: plugins.data.search,
|
||||
});
|
||||
|
||||
const startContract: InfraClientStartExports = {
|
||||
logViews,
|
||||
ContainerMetricsTable: createLazyContainerMetricsTable(getStartServices),
|
||||
HostMetricsTable: createLazyHostMetricsTable(getStartServices),
|
||||
PodMetricsTable: createLazyPodMetricsTable(getStartServices),
|
||||
};
|
||||
|
||||
return {
|
||||
ContainerMetricsTable: createLazyContainerMetricsTable(coreProvidersProps),
|
||||
HostMetricsTable: createLazyHostMetricsTable(coreProvidersProps),
|
||||
PodMetricsTable: createLazyPodMetricsTable(coreProvidersProps),
|
||||
};
|
||||
return startContract;
|
||||
}
|
||||
|
||||
stop() {}
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './configuration';
|
||||
export * from './status';
|
||||
export * from './log_views_client';
|
||||
export * from './log_views_service';
|
||||
export * from './types';
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ILogViewsClient } from './types';
|
||||
|
||||
export const createLogViewsClientMock = (): jest.Mocked<ILogViewsClient> => ({
|
||||
getLogView: jest.fn(),
|
||||
getResolvedLogView: jest.fn(),
|
||||
getResolvedLogViewStatus: jest.fn(),
|
||||
putLogView: jest.fn(),
|
||||
resolveLogView: jest.fn(),
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { ISearchGeneric } from 'src/plugins/data/public';
|
||||
import { DataViewsContract } from 'src/plugins/data_views/public';
|
||||
import {
|
||||
getLogViewResponsePayloadRT,
|
||||
getLogViewUrl,
|
||||
putLogViewRequestPayloadRT,
|
||||
} from '../../../common/http_api/log_views';
|
||||
import {
|
||||
FetchLogViewError,
|
||||
FetchLogViewStatusError,
|
||||
LogView,
|
||||
LogViewAttributes,
|
||||
LogViewsStaticConfig,
|
||||
LogViewStatus,
|
||||
PutLogViewError,
|
||||
ResolvedLogView,
|
||||
resolveLogView,
|
||||
} from '../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||
import { ILogViewsClient } from './types';
|
||||
|
||||
export class LogViewsClient implements ILogViewsClient {
|
||||
constructor(
|
||||
private readonly dataViews: DataViewsContract,
|
||||
private readonly http: HttpStart,
|
||||
private readonly search: ISearchGeneric,
|
||||
private readonly config: LogViewsStaticConfig
|
||||
) {}
|
||||
|
||||
public async getLogView(logViewId: string): Promise<LogView> {
|
||||
const response = await this.http.get(getLogViewUrl(logViewId)).catch((error) => {
|
||||
throw new FetchLogViewError(`Failed to fetch log view "${logViewId}": ${error}`);
|
||||
});
|
||||
|
||||
const { data } = decodeOrThrow(
|
||||
getLogViewResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new FetchLogViewError(`Failed to decode log view "${logViewId}": ${message}"`)
|
||||
)(response);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async getResolvedLogView(logViewId: string): Promise<ResolvedLogView> {
|
||||
const logView = await this.getLogView(logViewId);
|
||||
const resolvedLogView = await this.resolveLogView(logView.attributes);
|
||||
return resolvedLogView;
|
||||
}
|
||||
|
||||
public async getResolvedLogViewStatus(resolvedLogView: ResolvedLogView): Promise<LogViewStatus> {
|
||||
const indexStatus = await this.search({
|
||||
params: {
|
||||
ignore_unavailable: true,
|
||||
allow_no_indices: true,
|
||||
index: resolvedLogView.indices,
|
||||
size: 0,
|
||||
terminate_after: 1,
|
||||
track_total_hits: 1,
|
||||
},
|
||||
})
|
||||
.toPromise()
|
||||
.then(
|
||||
({ rawResponse }) => {
|
||||
if (rawResponse._shards.total <= 0) {
|
||||
return 'missing' as const;
|
||||
}
|
||||
|
||||
const totalHits = decodeTotalHits(rawResponse.hits.total);
|
||||
if (typeof totalHits === 'number' ? totalHits > 0 : totalHits.value > 0) {
|
||||
return 'available' as const;
|
||||
}
|
||||
|
||||
return 'empty' as const;
|
||||
},
|
||||
(err) => {
|
||||
if (err.status === 404) {
|
||||
return 'missing' as const;
|
||||
}
|
||||
throw new FetchLogViewStatusError(
|
||||
`Failed to check status of log indices of "${resolvedLogView.indices}": ${err}`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
index: indexStatus,
|
||||
};
|
||||
}
|
||||
|
||||
public async putLogView(
|
||||
logViewId: string,
|
||||
logViewAttributes: Partial<LogViewAttributes>
|
||||
): Promise<LogView> {
|
||||
const response = await this.http
|
||||
.put(getLogViewUrl(logViewId), {
|
||||
body: JSON.stringify(putLogViewRequestPayloadRT.encode({ attributes: logViewAttributes })),
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new PutLogViewError(`Failed to write log view "${logViewId}": ${error}`);
|
||||
});
|
||||
|
||||
const { data } = decodeOrThrow(
|
||||
getLogViewResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new PutLogViewError(`Failed to decode written log view "${logViewId}": ${message}"`)
|
||||
)(response);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async resolveLogView(logViewAttributes: LogViewAttributes): Promise<ResolvedLogView> {
|
||||
return await resolveLogView(logViewAttributes, this.dataViews, this.config);
|
||||
}
|
||||
}
|
||||
|
||||
const decodeTotalHits = decodeOrThrow(
|
||||
rt.union([
|
||||
rt.number,
|
||||
rt.type({
|
||||
value: rt.number,
|
||||
}),
|
||||
])
|
||||
);
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createLogViewsClientMock } from './log_views_client.mock';
|
||||
import { LogViewsServiceStart } from './types';
|
||||
|
||||
export const createLogViewsServiceStartMock = () => ({
|
||||
client: createLogViewsClientMock(),
|
||||
});
|
||||
|
||||
export const _ensureTypeCompatibility = (): LogViewsServiceStart =>
|
||||
createLogViewsServiceStartMock();
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogViewsStaticConfig } from '../../../common/log_views';
|
||||
import { LogViewsClient } from './log_views_client';
|
||||
import { LogViewsServiceStartDeps, LogViewsServiceSetup, LogViewsServiceStart } from './types';
|
||||
|
||||
export class LogViewsService {
|
||||
constructor(private readonly config: LogViewsStaticConfig) {}
|
||||
|
||||
public setup(): LogViewsServiceSetup {}
|
||||
|
||||
public start({ dataViews, http, search }: LogViewsServiceStartDeps): LogViewsServiceStart {
|
||||
const client = new LogViewsClient(dataViews, http, search.search, this.config);
|
||||
|
||||
return {
|
||||
client,
|
||||
};
|
||||
}
|
||||
}
|
36
x-pack/plugins/infra/public/services/log_views/types.ts
Normal file
36
x-pack/plugins/infra/public/services/log_views/types.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { ISearchStart } from 'src/plugins/data/public';
|
||||
import { DataViewsContract } from 'src/plugins/data_views/public';
|
||||
import {
|
||||
LogView,
|
||||
LogViewAttributes,
|
||||
LogViewStatus,
|
||||
ResolvedLogView,
|
||||
} from '../../../common/log_views';
|
||||
|
||||
export type LogViewsServiceSetup = void;
|
||||
|
||||
export interface LogViewsServiceStart {
|
||||
client: ILogViewsClient;
|
||||
}
|
||||
|
||||
export interface LogViewsServiceStartDeps {
|
||||
dataViews: DataViewsContract;
|
||||
http: HttpStart;
|
||||
search: ISearchStart;
|
||||
}
|
||||
|
||||
export interface ILogViewsClient {
|
||||
getLogView(logViewId: string): Promise<LogView>;
|
||||
getResolvedLogViewStatus(resolvedLogView: ResolvedLogView): Promise<LogViewStatus>;
|
||||
getResolvedLogView(logViewId: string): Promise<ResolvedLogView>;
|
||||
putLogView(logViewId: string, logViewAttributes: Partial<LogViewAttributes>): Promise<LogView>;
|
||||
resolveLogView(logViewAttributes: LogViewAttributes): Promise<ResolvedLogView>;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import faker from 'faker';
|
||||
import { LogEntry } from '../../common/log_entry';
|
||||
import { LogSourceConfiguration } from '../containers/logs/log_source';
|
||||
import { LogViewColumnConfiguration } from '../../common/log_views';
|
||||
|
||||
export const ENTRIES_EMPTY = {
|
||||
data: {
|
||||
|
@ -21,7 +21,7 @@ export function generateFakeEntries(
|
|||
count: number,
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
columns: LogSourceConfiguration['configuration']['logColumns']
|
||||
columns: LogViewColumnConfiguration[]
|
||||
): LogEntry[] {
|
||||
const entries: LogEntry[] = [];
|
||||
const timestampStep = Math.floor((endTimestamp - startTimestamp) / count);
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { GetLogSourceConfigurationSuccessResponsePayload } from '../../common/http_api/log_sources';
|
||||
|
||||
export const DEFAULT_SOURCE_CONFIGURATION: GetLogSourceConfigurationSuccessResponsePayload = {
|
||||
data: {
|
||||
id: 'default',
|
||||
version: 'WzQwNiwxXQ==',
|
||||
updatedAt: 1608559663482,
|
||||
origin: 'stored',
|
||||
configuration: {
|
||||
name: 'Default',
|
||||
description: '',
|
||||
logIndices: {
|
||||
type: 'index_pattern',
|
||||
indexPatternId: 'some-test-id',
|
||||
},
|
||||
fields: {
|
||||
container: 'container.id',
|
||||
host: 'host.name',
|
||||
pod: 'kubernetes.pod.uid',
|
||||
tiebreaker: '_doc',
|
||||
timestamp: '@timestamp',
|
||||
message: ['message'],
|
||||
},
|
||||
logColumns: [
|
||||
{
|
||||
timestampColumn: {
|
||||
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: ' eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: {
|
||||
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
import type { CoreSetup, CoreStart, Plugin as PluginClass } from 'kibana/public';
|
||||
import { IHttpFetchError } from 'src/core/public';
|
||||
import type { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import type { DataViewsPublicPluginStart } from '../../../../src/plugins/data_views/public';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import type { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public';
|
||||
|
@ -27,15 +28,18 @@ import type {
|
|||
} from '../../observability/public';
|
||||
// import type { OsqueryPluginStart } from '../../osquery/public';
|
||||
import type { SpacesPluginStart } from '../../spaces/public';
|
||||
import { UnwrapPromise } from '../common/utility_types';
|
||||
import type {
|
||||
SourceProviderProps,
|
||||
UseNodeMetricsTableOptions,
|
||||
} from './components/infrastructure_node_metrics_tables/shared';
|
||||
import { LogViewsServiceStart } from './services/log_views';
|
||||
|
||||
// Our own setup and start contract values
|
||||
export type InfraClientSetupExports = void;
|
||||
|
||||
export interface InfraClientStartExports {
|
||||
logViews: LogViewsServiceStart;
|
||||
ContainerMetricsTable: (
|
||||
props: UseNodeMetricsTableOptions & Partial<SourceProviderProps>
|
||||
) => JSX.Element;
|
||||
|
@ -61,6 +65,7 @@ export interface InfraClientSetupDeps {
|
|||
export interface InfraClientStartDeps {
|
||||
data: DataPublicPluginStart;
|
||||
dataEnhanced: DataEnhancedStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
observability: ObservabilityPublicStart;
|
||||
spaces: SpacesPluginStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
|
@ -79,6 +84,8 @@ export type InfraClientPluginClass = PluginClass<
|
|||
InfraClientSetupDeps,
|
||||
InfraClientStartDeps
|
||||
>;
|
||||
export type InfraClientStartServicesAccessor = InfraClientCoreSetup['getStartServices'];
|
||||
export type InfraClientStartServices = UnwrapPromise<ReturnType<InfraClientStartServicesAccessor>>;
|
||||
|
||||
export interface InfraHttpError extends IHttpFetchError {
|
||||
readonly body?: {
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { encode } from 'rison-node';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { encode } from 'rison-node';
|
||||
import { FetchData, FetchDataParams, LogsFetchDataResponse } from '../../../observability/public';
|
||||
import { DEFAULT_SOURCE_ID, TIMESTAMP_FIELD } from '../../common/constants';
|
||||
import { callFetchLogSourceConfigurationAPI } from '../containers/logs/log_source/api/fetch_log_source_configuration';
|
||||
import { callFetchLogSourceStatusAPI } from '../containers/logs/log_source/api/fetch_log_source_status';
|
||||
import { InfraClientCoreSetup, InfraClientStartDeps } from '../types';
|
||||
import { resolveLogSourceConfiguration } from '../../common/log_sources';
|
||||
import { InfraClientStartDeps, InfraClientStartServicesAccessor } from '../types';
|
||||
|
||||
interface StatsAggregation {
|
||||
buckets: Array<{
|
||||
|
@ -34,37 +31,32 @@ interface LogParams {
|
|||
|
||||
type StatsAndSeries = Pick<LogsFetchDataResponse, 'stats' | 'series'>;
|
||||
|
||||
export function getLogsHasDataFetcher(getStartServices: InfraClientCoreSetup['getStartServices']) {
|
||||
export function getLogsHasDataFetcher(getStartServices: InfraClientStartServicesAccessor) {
|
||||
return async () => {
|
||||
const [core] = await getStartServices();
|
||||
const sourceStatus = await callFetchLogSourceStatusAPI(DEFAULT_SOURCE_ID, core.http.fetch);
|
||||
const [, , { logViews }] = await getStartServices();
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_SOURCE_ID);
|
||||
const logViewStatus = await logViews.client.getResolvedLogViewStatus(resolvedLogView);
|
||||
|
||||
const hasData = logViewStatus.index === 'available';
|
||||
const indices = resolvedLogView.indices;
|
||||
|
||||
return {
|
||||
hasData: sourceStatus.data.logIndexStatus === 'available',
|
||||
indices: sourceStatus.data.indices,
|
||||
hasData,
|
||||
indices,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function getLogsOverviewDataFetcher(
|
||||
getStartServices: InfraClientCoreSetup['getStartServices']
|
||||
getStartServices: InfraClientStartServicesAccessor
|
||||
): FetchData<LogsFetchDataResponse> {
|
||||
return async (params) => {
|
||||
const [core, startPlugins] = await getStartServices();
|
||||
const { data } = startPlugins;
|
||||
|
||||
const sourceConfiguration = await callFetchLogSourceConfigurationAPI(
|
||||
DEFAULT_SOURCE_ID,
|
||||
core.http.fetch
|
||||
);
|
||||
|
||||
const resolvedLogSourceConfiguration = await resolveLogSourceConfiguration(
|
||||
sourceConfiguration.data.configuration,
|
||||
startPlugins.data.indexPatterns
|
||||
);
|
||||
const [, { data }, { logViews }] = await getStartServices();
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_SOURCE_ID);
|
||||
|
||||
const { stats, series } = await fetchLogsOverview(
|
||||
{
|
||||
index: resolvedLogSourceConfiguration.indices,
|
||||
index: resolvedLogView.indices,
|
||||
},
|
||||
params,
|
||||
data
|
||||
|
|
|
@ -6,26 +6,14 @@
|
|||
*/
|
||||
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { of } from 'rxjs';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { dataPluginMock } from 'src/plugins/data/public/mocks';
|
||||
import { createIndexPatternMock } from '../../common/dependency_mocks/index_patterns';
|
||||
import { GetLogSourceConfigurationSuccessResponsePayload } from '../../common/http_api/log_sources/get_log_source_configuration';
|
||||
import { callFetchLogSourceConfigurationAPI } from '../containers/logs/log_source/api/fetch_log_source_configuration';
|
||||
import { callFetchLogSourceStatusAPI } from '../containers/logs/log_source/api/fetch_log_source_status';
|
||||
import { createResolvedLogViewMock } from '../../common/log_views/resolved_log_view.mock';
|
||||
import { createInfraPluginStartMock } from '../mocks';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './logs_overview_fetchers';
|
||||
|
||||
jest.mock('../containers/logs/log_source/api/fetch_log_source_status');
|
||||
const mockedCallFetchLogSourceStatusAPI = callFetchLogSourceStatusAPI as jest.MockedFunction<
|
||||
typeof callFetchLogSourceStatusAPI
|
||||
>;
|
||||
|
||||
jest.mock('../containers/logs/log_source/api/fetch_log_source_configuration');
|
||||
const mockedCallFetchLogSourceConfigurationAPI =
|
||||
callFetchLogSourceConfigurationAPI as jest.MockedFunction<
|
||||
typeof callFetchLogSourceConfigurationAPI
|
||||
>;
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
absoluteTime: { start: 1593430680000, end: 1593430800000 },
|
||||
relativeTime: { start: 'now-2m', end: 'now' }, // Doesn't matter for the test
|
||||
|
@ -36,155 +24,110 @@ const DEFAULT_PARAMS = {
|
|||
function setup() {
|
||||
const core = coreMock.createStart();
|
||||
const data = dataPluginMock.createStartContract();
|
||||
const pluginStart = createInfraPluginStartMock();
|
||||
const pluginDeps = { data } as InfraClientStartDeps;
|
||||
|
||||
// `dataResponder.mockReturnValue()` will be the `response` in
|
||||
//
|
||||
// const searcher = data.search.getSearchStrategy('sth');
|
||||
// searcher.search(...).subscribe((**response**) => {});
|
||||
//
|
||||
const dataResponder = jest.fn();
|
||||
const dataSearch = data.search.search as jest.MockedFunction<typeof data.search.search>;
|
||||
|
||||
(data.indexPatterns.get as jest.Mock).mockResolvedValue(
|
||||
createIndexPatternMock({
|
||||
id: 'test-index-pattern',
|
||||
title: 'log-indices-*',
|
||||
timeFieldName: '@timestamp',
|
||||
type: undefined,
|
||||
fields: [
|
||||
{
|
||||
name: 'event.dataset',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'runtime_field',
|
||||
type: 'string',
|
||||
runtimeField: {
|
||||
type: 'keyword',
|
||||
script: {
|
||||
source: 'emit("runtime value")',
|
||||
},
|
||||
},
|
||||
esTypes: ['keyword'],
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
const mockedGetStartServices = jest.fn(() =>
|
||||
Promise.resolve<[CoreStart, InfraClientStartDeps, InfraClientStartExports]>([
|
||||
core,
|
||||
pluginDeps,
|
||||
pluginStart,
|
||||
])
|
||||
);
|
||||
|
||||
(data.search.search as jest.Mock).mockReturnValue({
|
||||
subscribe: (progress: Function, error: Function, finish: Function) => {
|
||||
progress(dataResponder());
|
||||
finish();
|
||||
},
|
||||
});
|
||||
|
||||
const mockedGetStartServices = jest.fn(() => {
|
||||
const deps = { data };
|
||||
return Promise.resolve([
|
||||
core as CoreStart,
|
||||
deps as InfraClientStartDeps,
|
||||
{} as InfraClientStartExports,
|
||||
]) as Promise<[CoreStart, InfraClientStartDeps, InfraClientStartExports]>;
|
||||
});
|
||||
return { core, mockedGetStartServices, dataResponder };
|
||||
return { core, dataSearch, mockedGetStartServices, pluginStart };
|
||||
}
|
||||
|
||||
describe('Logs UI Observability Homepage Functions', () => {
|
||||
describe('getLogsHasDataFetcher()', () => {
|
||||
beforeEach(() => {
|
||||
mockedCallFetchLogSourceStatusAPI.mockReset();
|
||||
});
|
||||
it('should return true when non-empty indices exist', async () => {
|
||||
const { mockedGetStartServices } = setup();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
mockedCallFetchLogSourceStatusAPI.mockResolvedValue({
|
||||
data: { logIndexStatus: 'available', indices: 'test-index' },
|
||||
describe('getLogsHasDataFetcher()', () => {
|
||||
it('should return true when non-empty indices exist', async () => {
|
||||
const { mockedGetStartServices, pluginStart } = setup();
|
||||
|
||||
pluginStart.logViews.client.getResolvedLogView.mockResolvedValue(
|
||||
createResolvedLogViewMock({ indices: 'test-index' })
|
||||
);
|
||||
pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({
|
||||
index: 'available',
|
||||
});
|
||||
|
||||
const hasData = getLogsHasDataFetcher(mockedGetStartServices);
|
||||
const response = await hasData();
|
||||
|
||||
expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1);
|
||||
expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual({ hasData: true, indices: 'test-index' });
|
||||
});
|
||||
|
||||
it('should return false when only empty indices exist', async () => {
|
||||
const { mockedGetStartServices } = setup();
|
||||
const { mockedGetStartServices, pluginStart } = setup();
|
||||
|
||||
mockedCallFetchLogSourceStatusAPI.mockResolvedValue({
|
||||
data: { logIndexStatus: 'empty', indices: 'test-index' },
|
||||
pluginStart.logViews.client.getResolvedLogView.mockResolvedValue(
|
||||
createResolvedLogViewMock({ indices: 'test-index' })
|
||||
);
|
||||
pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({
|
||||
index: 'empty',
|
||||
});
|
||||
|
||||
const hasData = getLogsHasDataFetcher(mockedGetStartServices);
|
||||
const response = await hasData();
|
||||
|
||||
expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1);
|
||||
expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual({ hasData: false, indices: 'test-index' });
|
||||
});
|
||||
|
||||
it('should return false when no index exists', async () => {
|
||||
const { mockedGetStartServices } = setup();
|
||||
const { mockedGetStartServices, pluginStart } = setup();
|
||||
|
||||
mockedCallFetchLogSourceStatusAPI.mockResolvedValue({
|
||||
data: { logIndexStatus: 'missing', indices: 'test-index' },
|
||||
pluginStart.logViews.client.getResolvedLogView.mockResolvedValue(
|
||||
createResolvedLogViewMock({ indices: 'test-index' })
|
||||
);
|
||||
pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({
|
||||
index: 'missing',
|
||||
});
|
||||
|
||||
const hasData = getLogsHasDataFetcher(mockedGetStartServices);
|
||||
const response = await hasData();
|
||||
|
||||
expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1);
|
||||
expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual({ hasData: false, indices: 'test-index' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLogsOverviewDataFetcher()', () => {
|
||||
beforeAll(() => {
|
||||
mockedCallFetchLogSourceConfigurationAPI.mockResolvedValue({
|
||||
data: {
|
||||
configuration: {
|
||||
logIndices: {
|
||||
type: 'index_pattern',
|
||||
indexPatternId: 'test-index-pattern',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as GetLogSourceConfigurationSuccessResponsePayload);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockedCallFetchLogSourceConfigurationAPI.mockReset();
|
||||
});
|
||||
|
||||
it('should work', async () => {
|
||||
const { mockedGetStartServices, dataResponder } = setup();
|
||||
const { mockedGetStartServices, dataSearch, pluginStart } = setup();
|
||||
|
||||
dataResponder.mockReturnValue({
|
||||
rawResponse: {
|
||||
aggregations: {
|
||||
stats: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'nginx',
|
||||
doc_count: 250, // Count is for 2 minutes
|
||||
series: {
|
||||
buckets: [
|
||||
// Counts are per 30 seconds
|
||||
{ key: 1593430680000, doc_count: 25 },
|
||||
{ key: 1593430710000, doc_count: 50 },
|
||||
{ key: 1593430740000, doc_count: 75 },
|
||||
{ key: 1593430770000, doc_count: 100 },
|
||||
],
|
||||
pluginStart.logViews.client.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock());
|
||||
|
||||
dataSearch.mockReturnValue(
|
||||
of({
|
||||
rawResponse: {
|
||||
aggregations: {
|
||||
stats: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'nginx',
|
||||
doc_count: 250, // Count is for 2 minutes
|
||||
series: {
|
||||
buckets: [
|
||||
// Counts are per 30 seconds
|
||||
{ key: 1593430680000, doc_count: 25 },
|
||||
{ key: 1593430710000, doc_count: 50 },
|
||||
{ key: 1593430740000, doc_count: 75 },
|
||||
{ key: 1593430770000, doc_count: 100 },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const fetchData = getLogsOverviewDataFetcher(mockedGetStartServices);
|
||||
const response = await fetchData(DEFAULT_PARAMS);
|
||||
|
|
|
@ -23,11 +23,10 @@ export const useObservable = <
|
|||
createObservableOnce: (inputValues: Observable<InputValues>) => OutputObservable,
|
||||
inputValues: InputValues
|
||||
) => {
|
||||
const [inputValues$] = useState(() => new BehaviorSubject<InputValues>(inputValues));
|
||||
const [output$] = useState(() => createObservableOnce(inputValues$));
|
||||
const [output$, next] = useBehaviorSubject(createObservableOnce, () => inputValues);
|
||||
|
||||
useEffect(() => {
|
||||
inputValues$.next(inputValues);
|
||||
next(inputValues);
|
||||
// `inputValues` can't be statically analyzed
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, inputValues);
|
||||
|
@ -35,6 +34,19 @@ export const useObservable = <
|
|||
return output$;
|
||||
};
|
||||
|
||||
export const useBehaviorSubject = <
|
||||
InputValue,
|
||||
OutputValue,
|
||||
OutputObservable extends Observable<OutputValue>
|
||||
>(
|
||||
deriveObservableOnce: (input$: Observable<InputValue>) => OutputObservable,
|
||||
createInitialValue: () => InputValue
|
||||
) => {
|
||||
const [subject$] = useState(() => new BehaviorSubject<InputValue>(createInitialValue()));
|
||||
const [output$] = useState(() => deriveObservableOnce(subject$));
|
||||
return [output$, subject$.next.bind(subject$)] as const;
|
||||
};
|
||||
|
||||
export const useObservableState = <State, InitialState>(
|
||||
state$: Observable<State>,
|
||||
initialState: InitialState | (() => InitialState)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue