Skip to content

Commit

Permalink
[Metrics UI] Allow users to create alerts from the central Alerts UI (#…
Browse files Browse the repository at this point in the history
…63803) (#63907)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
simianhacker and elasticmachine committed Apr 20, 2020
1 parent e364f9d commit 5bf491b
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 243 deletions.
188 changes: 188 additions & 0 deletions x-pack/plugins/infra/common/http_api/source_api.ts
Original file line number Diff line number Diff line change
@@ -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> {}
111 changes: 32 additions & 79 deletions x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>;
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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'} />
Expand All @@ -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}
Expand All @@ -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'} />
Expand Down Expand Up @@ -347,7 +317,6 @@ export const Expressions: React.FC<Props> = props => {

interface ExpressionRowProps {
fields: IFieldType[];
canEditAggField: boolean;
expressionId: number;
expression: MetricExpression;
errors: IErrorObject;
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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', {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/public/containers/source/source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
Loading

0 comments on commit 5bf491b

Please sign in to comment.