[Metrics UI] Allow users to create alerts from the central Alerts UI (#63803) (#63907)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Chris Cowan 2020-04-20 13:43:17 -07:00 committed by GitHub
parent e364f9da43
commit 5bf491b469
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 402 additions and 243 deletions

View file

@ -0,0 +1,188 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as rt from 'io-ts';
import moment from 'moment';
import { pipe } from 'fp-ts/lib/pipeable';
import { chain } from 'fp-ts/lib/Either';
export const TimestampFromString = new rt.Type<number, string>(
'TimestampFromString',
(input): input is number => typeof input === 'number',
(input, context) =>
pipe(
rt.string.validate(input, context),
chain(stringInput => {
const momentValue = moment(stringInput);
return momentValue.isValid()
? rt.success(momentValue.valueOf())
: rt.failure(stringInput, context);
})
),
output => new Date(output).toISOString()
);
/**
* Stored source configuration as read from and written to saved objects
*/
const SavedSourceConfigurationFieldsRuntimeType = rt.partial({
container: rt.string,
host: rt.string,
pod: rt.string,
tiebreaker: rt.string,
timestamp: rt.string,
});
export const SavedSourceConfigurationTimestampColumnRuntimeType = rt.type({
timestampColumn: rt.type({
id: rt.string,
}),
});
export const SavedSourceConfigurationMessageColumnRuntimeType = rt.type({
messageColumn: rt.type({
id: rt.string,
}),
});
export const SavedSourceConfigurationFieldColumnRuntimeType = rt.type({
fieldColumn: rt.type({
id: rt.string,
field: rt.string,
}),
});
export const SavedSourceConfigurationColumnRuntimeType = rt.union([
SavedSourceConfigurationTimestampColumnRuntimeType,
SavedSourceConfigurationMessageColumnRuntimeType,
SavedSourceConfigurationFieldColumnRuntimeType,
]);
export const SavedSourceConfigurationRuntimeType = rt.partial({
name: rt.string,
description: rt.string,
metricAlias: rt.string,
logAlias: rt.string,
fields: SavedSourceConfigurationFieldsRuntimeType,
logColumns: rt.array(SavedSourceConfigurationColumnRuntimeType),
});
export interface InfraSavedSourceConfiguration
extends rt.TypeOf<typeof SavedSourceConfigurationRuntimeType> {}
export const pickSavedSourceConfiguration = (
value: InfraSourceConfiguration
): InfraSavedSourceConfiguration => {
const { name, description, metricAlias, logAlias, fields, logColumns } = value;
const { container, host, pod, tiebreaker, timestamp } = fields;
return {
name,
description,
metricAlias,
logAlias,
fields: { container, host, pod, tiebreaker, timestamp },
logColumns,
};
};
/**
* Static source configuration as read from the configuration file
*/
const StaticSourceConfigurationFieldsRuntimeType = rt.partial({
...SavedSourceConfigurationFieldsRuntimeType.props,
message: rt.array(rt.string),
});
export const StaticSourceConfigurationRuntimeType = rt.partial({
name: rt.string,
description: rt.string,
metricAlias: rt.string,
logAlias: rt.string,
fields: StaticSourceConfigurationFieldsRuntimeType,
logColumns: rt.array(SavedSourceConfigurationColumnRuntimeType),
});
export interface InfraStaticSourceConfiguration
extends rt.TypeOf<typeof StaticSourceConfigurationRuntimeType> {}
/**
* Full source configuration type after all cleanup has been done at the edges
*/
const SourceConfigurationFieldsRuntimeType = rt.type({
...StaticSourceConfigurationFieldsRuntimeType.props,
});
export const SourceConfigurationRuntimeType = rt.type({
...SavedSourceConfigurationRuntimeType.props,
fields: SourceConfigurationFieldsRuntimeType,
logColumns: rt.array(SavedSourceConfigurationColumnRuntimeType),
});
export const SourceRuntimeType = rt.intersection([
rt.type({
id: rt.string,
origin: rt.keyof({
fallback: null,
internal: null,
stored: null,
}),
configuration: SourceConfigurationRuntimeType,
}),
rt.partial({
version: rt.string,
updatedAt: rt.number,
}),
]);
export interface InfraSourceConfiguration
extends rt.TypeOf<typeof SourceConfigurationRuntimeType> {}
export interface InfraSource extends rt.TypeOf<typeof SourceRuntimeType> {}
const SourceStatusFieldRuntimeType = rt.type({
name: rt.string,
type: rt.string,
searchable: rt.boolean,
aggregatable: rt.boolean,
displayable: rt.boolean,
});
const SourceStatusRuntimeType = rt.type({
logIndicesExist: rt.boolean,
metricIndicesExist: rt.boolean,
indexFields: rt.array(SourceStatusFieldRuntimeType),
});
export const SourceResponseRuntimeType = rt.type({
source: SourceRuntimeType,
status: SourceStatusRuntimeType,
});
export type SourceResponse = rt.TypeOf<typeof SourceResponseRuntimeType>;
/**
* Saved object type with metadata
*/
export const SourceConfigurationSavedObjectRuntimeType = rt.intersection([
rt.type({
id: rt.string,
attributes: SavedSourceConfigurationRuntimeType,
}),
rt.partial({
version: rt.string,
updated_at: TimestampFromString,
}),
]);
export interface SourceConfigurationSavedObject
extends rt.TypeOf<typeof SourceConfigurationSavedObjectRuntimeType> {}

View file

@ -17,9 +17,6 @@ import {
import { IFieldType } from 'src/plugins/data/public';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiExpression } from '@elastic/eui';
import { EuiCallOut } from '@elastic/eui';
import { EuiLink } from '@elastic/eui';
import {
MetricExpressionParams,
Comparator,
@ -41,8 +38,8 @@ import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/ap
import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options';
import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar';
import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer';
import { useSource } from '../../../containers/source';
import { MetricsExplorerGroupBy } from '../../metrics_explorer/group_by';
import { useSourceViaHttp } from '../../../containers/source/use_source_via_http';
interface AlertContextMeta {
currentOptions?: Partial<MetricsExplorerOptions>;
@ -87,7 +84,12 @@ const defaultExpression = {
export const Expressions: React.FC<Props> = props => {
const { setAlertParams, alertParams, errors, alertsContext } = props;
const { source, createDerivedIndexPattern } = useSource({ sourceId: 'default' });
const { source, createDerivedIndexPattern } = useSourceViaHttp({
sourceId: 'default',
type: 'metrics',
fetch: alertsContext.http.fetch,
toastWarning: alertsContext.toastNotifications.addWarning,
});
const [timeSize, setTimeSize] = useState<number | undefined>(1);
const [timeUnit, setTimeUnit] = useState<TimeUnit>('m');
@ -208,40 +210,11 @@ export const Expressions: React.FC<Props> = props => {
setAlertParams('groupBy', md.currentOptions.groupBy);
}
setAlertParams('sourceId', source?.id);
} else {
setAlertParams('criteria', [defaultExpression]);
}
}, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps
// INFO: If there is metadata, you're in the metrics explorer context
const canAddConditions = !!alertsContext.metadata;
if (!canAddConditions && !alertParams.criteria) {
return (
<>
<EuiSpacer size={'m'} />
<EuiCallOut
title={
<>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.createAlertWarningBody"
defaultMessage="Create new metric threshold alerts from"
/>{' '}
<EuiLink href={'../app/metrics/explorer'}>
<FormattedMessage
id="xpack.infra.homePage.metricsExplorerTabTitle"
defaultMessage="Metrics Explorer"
/>
</EuiLink>
.
</>
}
color="warning"
iconType="help"
/>
<EuiSpacer size={'m'} />
</>
);
}
return (
<>
<EuiSpacer size={'m'} />
@ -258,7 +231,6 @@ export const Expressions: React.FC<Props> = props => {
alertParams.criteria.map((e, idx) => {
return (
<ExpressionRow
canEditAggField={canAddConditions}
canDelete={alertParams.criteria.length > 1}
fields={derivedIndexPattern.fields}
remove={removeExpression}
@ -281,20 +253,18 @@ export const Expressions: React.FC<Props> = props => {
/>
<div>
{canAddConditions && (
<EuiButtonEmpty
color={'primary'}
iconSide={'left'}
flush={'left'}
iconType={'plusInCircleFilled'}
onClick={addExpression}
>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.addCondition"
defaultMessage="Add condition"
/>
</EuiButtonEmpty>
)}
<EuiButtonEmpty
color={'primary'}
iconSide={'left'}
flush={'left'}
iconType={'plusInCircleFilled'}
onClick={addExpression}
>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.addCondition"
defaultMessage="Add condition"
/>
</EuiButtonEmpty>
</div>
<EuiSpacer size={'m'} />
@ -347,7 +317,6 @@ export const Expressions: React.FC<Props> = props => {
interface ExpressionRowProps {
fields: IFieldType[];
canEditAggField: boolean;
expressionId: number;
expression: MetricExpression;
errors: IErrorObject;
@ -424,20 +393,17 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = props => {
</StyledExpression>
{aggType !== 'count' && (
<StyledExpression>
{!props.canEditAggField && <DisabledAggField text={metric || ''} />}
{props.canEditAggField && (
<OfExpression
customAggTypesOptions={aggregationType}
aggField={metric}
fields={fields.map(f => ({
normalizedType: f.type,
name: f.name,
}))}
aggType={aggType}
errors={errors}
onChangeSelectedAggField={updateMetric}
/>
)}
<OfExpression
customAggTypesOptions={aggregationType}
aggField={metric}
fields={fields.map(f => ({
normalizedType: f.type,
name: f.name,
}))}
aggType={aggType}
errors={errors}
onChangeSelectedAggField={updateMetric}
/>
</StyledExpression>
)}
<StyledExpression>
@ -469,19 +435,6 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = props => {
);
};
export const DisabledAggField = ({ text }: { text: string }) => {
return (
<EuiExpression
description={i18n.translate('xpack.infra.metrics.alertFlyout.of.buttonLabel', {
defaultMessage: 'of',
})}
value={text}
isActive={false}
color={'secondary'}
/>
);
};
export const aggregationType: { [key: string]: any } = {
avg: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', {

View file

@ -21,7 +21,7 @@ import { updateSourceMutation } from './update_source.gql_query';
type Source = SourceQuery.Query['source'];
const pickIndexPattern = (source: Source | undefined, type: 'logs' | 'metrics' | 'both') => {
export const pickIndexPattern = (source: Source | undefined, type: 'logs' | 'metrics' | 'both') => {
if (!source) {
return 'unknown-index';
}

View file

@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useEffect, useMemo } from 'react';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import createContainer from 'constate';
import { HttpHandler } from 'target/types/core/public/http';
import { ToastInput } from 'target/types/core/public/notifications/toasts/toasts_api';
import {
SourceResponseRuntimeType,
SourceResponse,
InfraSource,
} from '../../../common/http_api/source_api';
import { useHTTPRequest } from '../../hooks/use_http_request';
import { throwErrors, createPlainError } from '../../../common/runtime_types';
export const pickIndexPattern = (
source: InfraSource | undefined,
type: 'logs' | 'metrics' | 'both'
) => {
if (!source) {
return 'unknown-index';
}
if (type === 'logs') {
return source.configuration.logAlias;
}
if (type === 'metrics') {
return source.configuration.metricAlias;
}
return `${source.configuration.logAlias},${source.configuration.metricAlias}`;
};
interface Props {
sourceId: string;
type: 'logs' | 'metrics' | 'both';
fetch?: HttpHandler;
toastWarning?: (input: ToastInput) => void;
}
export const useSourceViaHttp = ({
sourceId = 'default',
type = 'both',
fetch,
toastWarning,
}: Props) => {
const decodeResponse = (response: any) => {
return pipe(
SourceResponseRuntimeType.decode(response),
fold(throwErrors(createPlainError), identity)
);
};
const { error, loading, response, makeRequest } = useHTTPRequest<SourceResponse>(
`/api/metrics/source/${sourceId}/${type}`,
'GET',
null,
decodeResponse,
fetch,
toastWarning
);
useEffect(() => {
(async () => {
await makeRequest();
})();
}, [makeRequest]);
const createDerivedIndexPattern = (indexType: 'logs' | 'metrics' | 'both' = type) => {
return {
fields: response?.source ? response.status.indexFields : [],
title: pickIndexPattern(response?.source, indexType),
};
};
const source = useMemo(() => {
return response ? { ...response.source, status: response.status } : null;
}, [response]);
return {
createDerivedIndexPattern,
source,
loading,
error,
};
};
export const SourceViaHttp = createContainer(useSourceViaHttp);
export const [SourceViaHttpProvider, useSourceViaHttpContext] = SourceViaHttp;

View file

@ -7,28 +7,32 @@
import React, { useMemo, useState } from 'react';
import { IHttpFetchError } from 'src/core/public';
import { i18n } from '@kbn/i18n';
import { HttpHandler } from 'target/types/core/public/http';
import { ToastInput } from 'target/types/core/public/notifications/toasts/toasts_api';
import { useTrackedPromise } from '../utils/use_tracked_promise';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
export function useHTTPRequest<Response>(
pathname: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD',
body?: string,
decode: (response: any) => Response = response => response
body?: string | null,
decode: (response: any) => Response = response => response,
fetch?: HttpHandler,
toastWarning?: (input: ToastInput) => void
) {
const kibana = useKibana();
const fetch = kibana.services.http?.fetch;
const toasts = kibana.notifications.toasts;
const fetchService = fetch ? fetch : kibana.services.http?.fetch;
const toast = toastWarning ? toastWarning : kibana.notifications.toasts.warning;
const [response, setResponse] = useState<Response | null>(null);
const [error, setError] = useState<IHttpFetchError | null>(null);
const [request, makeRequest] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: () => {
if (!fetch) {
if (!fetchService) {
throw new Error('HTTP service is unavailable');
}
return fetch(pathname, {
return fetchService(pathname, {
method,
body,
});
@ -37,7 +41,7 @@ export function useHTTPRequest<Response>(
onReject: (e: unknown) => {
const err = e as IHttpFetchError;
setError(err);
toasts.warning({
toast({
toastLifeTimeMs: 3000,
title: i18n.translate('xpack.infra.useHTTPRequest.error.title', {
defaultMessage: `Error while fetching resource`,
@ -67,7 +71,7 @@ export function useHTTPRequest<Response>(
});
},
},
[pathname, body, method, fetch, toasts]
[pathname, body, method, fetch, toast]
);
const loading = useMemo(() => {

View file

@ -29,6 +29,7 @@ import {
initLogEntriesItemRoute,
} from './routes/log_entries';
import { initInventoryMetaRoute } from './routes/inventory_metadata';
import { initSourceRoute } from './routes/source';
export const initInfraServer = (libs: InfraBackendLibs) => {
const schema = makeExecutableSchema({
@ -48,6 +49,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => {
initGetLogEntryRateRoute(libs);
initSnapshotRoute(libs);
initNodeDetailsRoute(libs);
initSourceRoute(libs);
initValidateLogAnalysisIndicesRoute(libs);
initLogEntriesRoute(libs);
initLogEntriesHighlightsRoute(libs);

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { InfraSourceConfiguration } from './types';
import { InfraSourceConfiguration } from '../../../common/http_api/source_api';
export const defaultSourceConfiguration: InfraSourceConfiguration = {
name: 'Default',

View file

@ -7,4 +7,4 @@
export * from './defaults';
export * from './saved_object_mappings';
export * from './sources';
export * from './types';
export * from '../../../common/http_api/source_api';

View file

@ -5,7 +5,7 @@
*/
import { ElasticsearchMappingOf } from '../../utils/typed_elasticsearch_mappings';
import { InfraSavedSourceConfiguration } from './types';
import { InfraSavedSourceConfiguration } from '../../../common/http_api/source_api';
export const infraSourceConfigurationSavedObjectType = 'infrastructure-ui-source';

View file

@ -20,7 +20,8 @@ import {
pickSavedSourceConfiguration,
SourceConfigurationSavedObjectRuntimeType,
StaticSourceConfigurationRuntimeType,
} from './types';
InfraSource,
} from '../../../common/http_api/source_api';
import { InfraConfig } from '../../../server';
interface Libs {
@ -35,7 +36,10 @@ export class InfraSources {
this.libs = libs;
}
public async getSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) {
public async getSourceConfiguration(
requestContext: RequestHandlerContext,
sourceId: string
): Promise<InfraSource> {
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
const savedSourceConfiguration = await this.getInternalSourceConfiguration(sourceId)

View file

@ -1,149 +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;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import moment from 'moment';
import { pipe } from 'fp-ts/lib/pipeable';
import { chain } from 'fp-ts/lib/Either';
export const TimestampFromString = new runtimeTypes.Type<number, string>(
'TimestampFromString',
(input): input is number => typeof input === 'number',
(input, context) =>
pipe(
runtimeTypes.string.validate(input, context),
chain(stringInput => {
const momentValue = moment(stringInput);
return momentValue.isValid()
? runtimeTypes.success(momentValue.valueOf())
: runtimeTypes.failure(stringInput, context);
})
),
output => new Date(output).toISOString()
);
/**
* Stored source configuration as read from and written to saved objects
*/
const SavedSourceConfigurationFieldsRuntimeType = runtimeTypes.partial({
container: runtimeTypes.string,
host: runtimeTypes.string,
pod: runtimeTypes.string,
tiebreaker: runtimeTypes.string,
timestamp: runtimeTypes.string,
});
export const SavedSourceConfigurationTimestampColumnRuntimeType = runtimeTypes.type({
timestampColumn: runtimeTypes.type({
id: runtimeTypes.string,
}),
});
export const SavedSourceConfigurationMessageColumnRuntimeType = runtimeTypes.type({
messageColumn: runtimeTypes.type({
id: runtimeTypes.string,
}),
});
export const SavedSourceConfigurationFieldColumnRuntimeType = runtimeTypes.type({
fieldColumn: runtimeTypes.type({
id: runtimeTypes.string,
field: runtimeTypes.string,
}),
});
export const SavedSourceConfigurationColumnRuntimeType = runtimeTypes.union([
SavedSourceConfigurationTimestampColumnRuntimeType,
SavedSourceConfigurationMessageColumnRuntimeType,
SavedSourceConfigurationFieldColumnRuntimeType,
]);
export const SavedSourceConfigurationRuntimeType = runtimeTypes.partial({
name: runtimeTypes.string,
description: runtimeTypes.string,
metricAlias: runtimeTypes.string,
logAlias: runtimeTypes.string,
fields: SavedSourceConfigurationFieldsRuntimeType,
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
});
export interface InfraSavedSourceConfiguration
extends runtimeTypes.TypeOf<typeof SavedSourceConfigurationRuntimeType> {}
export const pickSavedSourceConfiguration = (
value: InfraSourceConfiguration
): InfraSavedSourceConfiguration => {
const { name, description, metricAlias, logAlias, fields, logColumns } = value;
const { container, host, pod, tiebreaker, timestamp } = fields;
return {
name,
description,
metricAlias,
logAlias,
fields: { container, host, pod, tiebreaker, timestamp },
logColumns,
};
};
/**
* Static source configuration as read from the configuration file
*/
const StaticSourceConfigurationFieldsRuntimeType = runtimeTypes.partial({
...SavedSourceConfigurationFieldsRuntimeType.props,
message: runtimeTypes.array(runtimeTypes.string),
});
export const StaticSourceConfigurationRuntimeType = runtimeTypes.partial({
name: runtimeTypes.string,
description: runtimeTypes.string,
metricAlias: runtimeTypes.string,
logAlias: runtimeTypes.string,
fields: StaticSourceConfigurationFieldsRuntimeType,
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
});
export interface InfraStaticSourceConfiguration
extends runtimeTypes.TypeOf<typeof StaticSourceConfigurationRuntimeType> {}
/**
* Full source configuration type after all cleanup has been done at the edges
*/
const SourceConfigurationFieldsRuntimeType = runtimeTypes.type({
...StaticSourceConfigurationFieldsRuntimeType.props,
});
export const SourceConfigurationRuntimeType = runtimeTypes.type({
...SavedSourceConfigurationRuntimeType.props,
fields: SourceConfigurationFieldsRuntimeType,
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
});
export interface InfraSourceConfiguration
extends runtimeTypes.TypeOf<typeof SourceConfigurationRuntimeType> {}
/**
* Saved object type with metadata
*/
export const SourceConfigurationSavedObjectRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
id: runtimeTypes.string,
attributes: SavedSourceConfigurationRuntimeType,
}),
runtimeTypes.partial({
version: runtimeTypes.string,
updated_at: TimestampFromString,
}),
]);
export interface SourceConfigurationSavedObject
extends runtimeTypes.TypeOf<typeof SourceConfigurationSavedObjectRuntimeType> {}

View file

@ -26,7 +26,7 @@ import { InfraSources } from './lib/sources';
import { InfraServerPluginDeps } from './lib/adapters/framework';
import { METRICS_FEATURE, LOGS_FEATURE } from './features';
import { UsageCollector } from './usage/usage_collector';
import { InfraStaticSourceConfiguration } from './lib/sources/types';
import { InfraStaticSourceConfiguration } from '../common/http_api/source_api';
import { registerAlertTypes } from './lib/alerting';
export const config = {

View 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;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { SourceResponseRuntimeType } from '../../../common/http_api/source_api';
import { InfraBackendLibs } from '../../lib/infra_types';
import { InfraIndexType } from '../../graphql/types';
const typeToInfraIndexType = (value: string | undefined) => {
switch (value) {
case 'metrics':
return InfraIndexType.METRICS;
case 'logs':
return InfraIndexType.LOGS;
default:
return InfraIndexType.ANY;
}
};
export const initSourceRoute = (libs: InfraBackendLibs) => {
const { framework } = libs;
framework.registerRoute(
{
method: 'get',
path: '/api/metrics/source/{sourceId}/{type?}',
validate: {
params: schema.object({
sourceId: schema.string(),
type: schema.string(),
}),
},
},
async (requestContext, request, response) => {
try {
const { type, sourceId } = request.params;
const source = await libs.sources.getSourceConfiguration(requestContext, sourceId);
if (!source) {
return response.notFound();
}
const status = {
logIndicesExist: await libs.sourceStatus.hasLogIndices(requestContext, sourceId),
metricIndicesExist: await libs.sourceStatus.hasMetricIndices(requestContext, sourceId),
indexFields: await libs.fields.getFields(
requestContext,
sourceId,
typeToInfraIndexType(type)
),
};
return response.ok({
body: SourceResponseRuntimeType.encode({ source, status }),
});
} catch (error) {
return response.internalError({
body: error.message,
});
}
}
);
};