mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e364f9da43
commit
5bf491b469
13 changed files with 402 additions and 243 deletions
188
x-pack/plugins/infra/common/http_api/source_api.ts
Normal file
188
x-pack/plugins/infra/common/http_api/source_api.ts
Normal 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> {}
|
|
@ -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', {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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(() => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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> {}
|
|
@ -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 = {
|
||||
|
|
65
x-pack/plugins/infra/server/routes/source/index.ts
Normal file
65
x-pack/plugins/infra/server/routes/source/index.ts
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;
|
||||
* 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue