Skip to content

Commit

Permalink
[Logs UI] Fix initial selection of log threshold alert condition fiel…
Browse files Browse the repository at this point in the history
…d if missing from mapping (#86488)

This avoid selecting the `log.level` field as the default in log threshold alerts if it is not present in the mapping of at least one source index.
  • Loading branch information
weltenwort committed Dec 22, 2020
1 parent 96d63cc commit c2d1b2c
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 181 deletions.
74 changes: 57 additions & 17 deletions x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,29 +105,38 @@ const ThresholdRT = rt.type({

export type Threshold = rt.TypeOf<typeof ThresholdRT>;

export const CriterionRT = rt.type({
export const criterionRT = rt.type({
field: rt.string,
comparator: ComparatorRT,
value: rt.union([rt.string, rt.number]),
});
export type Criterion = rt.TypeOf<typeof criterionRT>;

export type Criterion = rt.TypeOf<typeof CriterionRT>;
export const criteriaRT = rt.array(CriterionRT);
export type Criteria = rt.TypeOf<typeof criteriaRT>;
export const partialCriterionRT = rt.partial(criterionRT.props);
export type PartialCriterion = rt.TypeOf<typeof partialCriterionRT>;

export const countCriteriaRT = criteriaRT;
export const countCriteriaRT = rt.array(criterionRT);
export type CountCriteria = rt.TypeOf<typeof countCriteriaRT>;

export const ratioCriteriaRT = rt.tuple([criteriaRT, criteriaRT]);
export const partialCountCriteriaRT = rt.array(partialCriterionRT);
export type PartialCountCriteria = rt.TypeOf<typeof partialCountCriteriaRT>;

export const ratioCriteriaRT = rt.tuple([countCriteriaRT, countCriteriaRT]);
export type RatioCriteria = rt.TypeOf<typeof ratioCriteriaRT>;

export const TimeUnitRT = rt.union([
export const partialRatioCriteriaRT = rt.tuple([partialCountCriteriaRT, partialCountCriteriaRT]);
export type PartialRatioCriteria = rt.TypeOf<typeof partialRatioCriteriaRT>;

export const partialCriteriaRT = rt.union([partialCountCriteriaRT, partialRatioCriteriaRT]);
export type PartialCriteria = rt.TypeOf<typeof partialCriteriaRT>;

export const timeUnitRT = rt.union([
rt.literal('s'),
rt.literal('m'),
rt.literal('h'),
rt.literal('d'),
]);
export type TimeUnit = rt.TypeOf<typeof TimeUnitRT>;
export type TimeUnit = rt.TypeOf<typeof timeUnitRT>;

export const timeSizeRT = rt.number;
export const groupByRT = rt.array(rt.string);
Expand All @@ -136,15 +145,18 @@ const RequiredAlertParamsRT = rt.type({
// NOTE: "count" would be better named as "threshold", but this would require a
// migration of encrypted saved objects, so we'll keep "count" until it's problematic.
count: ThresholdRT,
timeUnit: TimeUnitRT,
timeUnit: timeUnitRT,
timeSize: timeSizeRT,
});

const partialRequiredAlertParamsRT = rt.partial(RequiredAlertParamsRT.props);
export type PartialRequiredAlertParams = rt.TypeOf<typeof partialRequiredAlertParamsRT>;

const OptionalAlertParamsRT = rt.partial({
groupBy: groupByRT,
});

export const alertParamsRT = rt.intersection([
export const countAlertParamsRT = rt.intersection([
rt.type({
criteria: countCriteriaRT,
...RequiredAlertParamsRT.props,
Expand All @@ -153,8 +165,18 @@ export const alertParamsRT = rt.intersection([
...OptionalAlertParamsRT.props,
}),
]);
export type CountAlertParams = rt.TypeOf<typeof countAlertParamsRT>;

export type CountAlertParams = rt.TypeOf<typeof alertParamsRT>;
export const partialCountAlertParamsRT = rt.intersection([
rt.type({
criteria: partialCountCriteriaRT,
...RequiredAlertParamsRT.props,
}),
rt.partial({
...OptionalAlertParamsRT.props,
}),
]);
export type PartialCountAlertParams = rt.TypeOf<typeof partialCountAlertParamsRT>;

export const ratioAlertParamsRT = rt.intersection([
rt.type({
Expand All @@ -165,25 +187,43 @@ export const ratioAlertParamsRT = rt.intersection([
...OptionalAlertParamsRT.props,
}),
]);

export type RatioAlertParams = rt.TypeOf<typeof ratioAlertParamsRT>;

export const AlertParamsRT = rt.union([alertParamsRT, ratioAlertParamsRT]);
export type AlertParams = rt.TypeOf<typeof AlertParamsRT>;
export const partialRatioAlertParamsRT = rt.intersection([
rt.type({
criteria: partialRatioCriteriaRT,
...RequiredAlertParamsRT.props,
}),
rt.partial({
...OptionalAlertParamsRT.props,
}),
]);
export type PartialRatioAlertParams = rt.TypeOf<typeof partialRatioAlertParamsRT>;

export const alertParamsRT = rt.union([countAlertParamsRT, ratioAlertParamsRT]);
export type AlertParams = rt.TypeOf<typeof alertParamsRT>;

export const partialAlertParamsRT = rt.union([
partialCountAlertParamsRT,
partialRatioAlertParamsRT,
]);
export type PartialAlertParams = rt.TypeOf<typeof partialAlertParamsRT>;

export const isRatioAlert = (criteria: AlertParams['criteria']): criteria is RatioCriteria => {
export const isRatioAlert = (criteria: PartialCriteria): criteria is PartialRatioCriteria => {
return criteria.length > 0 && Array.isArray(criteria[0]) ? true : false;
};

export const isRatioAlertParams = (params: AlertParams): params is RatioAlertParams => {
return isRatioAlert(params.criteria);
};

export const getNumerator = (criteria: RatioCriteria): Criteria => {
export const getNumerator = <C extends RatioCriteria | PartialRatioCriteria>(criteria: C): C[0] => {
return criteria[0];
};

export const getDenominator = (criteria: RatioCriteria): Criteria => {
export const getDenominator = <C extends RatioCriteria | PartialRatioCriteria>(
criteria: C
): C[1] => {
return criteria[1];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import * as rt from 'io-ts';
import {
criteriaRT,
TimeUnitRT,
countCriteriaRT,
timeUnitRT,
timeSizeRT,
groupByRT,
} from '../../alerting/logs/log_threshold/types';
Expand Down Expand Up @@ -42,8 +42,8 @@ export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf<

export const getLogAlertsChartPreviewDataAlertParamsSubsetRT = rt.intersection([
rt.type({
criteria: criteriaRT,
timeUnit: TimeUnitRT,
criteria: countCriteriaRT,
timeUnit: timeUnitRT,
timeSize: timeSizeRT,
}),
rt.partial({
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/infra/common/utility_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ export type DeepPartial<T> = T extends any[]
interface DeepPartialArray<T> extends Array<DeepPartial<T>> {}

type DeepPartialObject<T> = { [P in keyof T]+?: DeepPartial<T[P]> };

export type ObjectEntry<T> = [keyof T, T[keyof T]];
export type ObjectEntries<T> = Array<ObjectEntry<T>>;
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import { i18n } from '@kbn/i18n';
import { IFieldType } from 'src/plugins/data/public';
import { Criterion } from './criterion';
import {
AlertParams,
Comparator,
Criteria as CriteriaType,
Criterion as CriterionType,
CountCriteria as CountCriteriaType,
RatioCriteria as RatioCriteriaType,
PartialAlertParams,
PartialCountCriteria as PartialCountCriteriaType,
PartialCriteria as PartialCriteriaType,
PartialCriterion as PartialCriterionType,
PartialRatioCriteria as PartialRatioCriteriaType,
isRatioAlert,
getNumerator,
getDenominator,
Expand All @@ -25,8 +24,6 @@ import { Errors, CriterionErrors } from '../../validation';
import { ExpressionLike } from './editor';
import { CriterionPreview } from './criterion_preview_chart';

const DEFAULT_CRITERIA = { field: 'log.level', comparator: Comparator.EQ, value: 'error' };

const QueryAText = i18n.translate('xpack.infra.logs.alerting.threshold.ratioCriteriaQueryAText', {
defaultMessage: 'Query A',
});
Expand All @@ -37,11 +34,12 @@ const QueryBText = i18n.translate('xpack.infra.logs.alerting.threshold.ratioCrit

interface SharedProps {
fields: IFieldType[];
criteria?: AlertParams['criteria'];
criteria?: PartialCriteriaType;
defaultCriterion: PartialCriterionType;
errors: Errors['criteria'];
alertParams: Partial<AlertParams>;
alertParams: PartialAlertParams;
sourceId: string;
updateCriteria: (criteria: AlertParams['criteria']) => void;
updateCriteria: (criteria: PartialCriteriaType) => void;
}

type CriteriaProps = SharedProps;
Expand All @@ -60,10 +58,10 @@ export const Criteria: React.FC<CriteriaProps> = (props) => {
interface CriteriaWrapperProps {
alertParams: SharedProps['alertParams'];
fields: SharedProps['fields'];
updateCriterion: (idx: number, params: Partial<CriterionType>) => void;
updateCriterion: (idx: number, params: PartialCriterionType) => void;
removeCriterion: (idx: number) => void;
addCriterion: () => void;
criteria: CriteriaType;
criteria: PartialCountCriteriaType;
errors: CriterionErrors;
sourceId: SharedProps['sourceId'];
isRatio?: boolean;
Expand Down Expand Up @@ -118,29 +116,24 @@ const CriteriaWrapper: React.FC<CriteriaWrapperProps> = (props) => {
);
};

interface RatioCriteriaProps {
alertParams: SharedProps['alertParams'];
fields: SharedProps['fields'];
criteria: RatioCriteriaType;
errors: Errors['criteria'];
sourceId: SharedProps['sourceId'];
updateCriteria: (criteria: AlertParams['criteria']) => void;
interface RatioCriteriaProps extends SharedProps {
criteria: PartialRatioCriteriaType;
}

const RatioCriteria: React.FC<RatioCriteriaProps> = (props) => {
const { criteria, errors, updateCriteria } = props;
const { criteria, defaultCriterion, errors, updateCriteria } = props;

const handleUpdateNumeratorCriteria = useCallback(
(criteriaParam: CriteriaType) => {
const nextCriteria: RatioCriteriaType = [criteriaParam, getDenominator(criteria)];
(criteriaParam: PartialCountCriteriaType) => {
const nextCriteria: PartialRatioCriteriaType = [criteriaParam, getDenominator(criteria)];
updateCriteria(nextCriteria);
},
[updateCriteria, criteria]
);

const handleUpdateDenominatorCriteria = useCallback(
(criteriaParam: CriteriaType) => {
const nextCriteria: RatioCriteriaType = [getNumerator(criteria), criteriaParam];
(criteriaParam: PartialCountCriteriaType) => {
const nextCriteria: PartialRatioCriteriaType = [getNumerator(criteria), criteriaParam];
updateCriteria(nextCriteria);
},
[updateCriteria, criteria]
Expand All @@ -150,13 +143,13 @@ const RatioCriteria: React.FC<RatioCriteriaProps> = (props) => {
updateCriterion: updateNumeratorCriterion,
addCriterion: addNumeratorCriterion,
removeCriterion: removeNumeratorCriterion,
} = useCriteriaState(getNumerator(criteria), handleUpdateNumeratorCriteria);
} = useCriteriaState(getNumerator(criteria), defaultCriterion, handleUpdateNumeratorCriteria);

const {
updateCriterion: updateDenominatorCriterion,
addCriterion: addDenominatorCriterion,
removeCriterion: removeDenominatorCriterion,
} = useCriteriaState(getDenominator(criteria), handleUpdateDenominatorCriteria);
} = useCriteriaState(getDenominator(criteria), defaultCriterion, handleUpdateDenominatorCriteria);

return (
<>
Expand Down Expand Up @@ -191,28 +184,17 @@ const RatioCriteria: React.FC<RatioCriteriaProps> = (props) => {
);
};

interface CountCriteriaProps {
alertParams: SharedProps['alertParams'];
fields: SharedProps['fields'];
criteria: CountCriteriaType;
errors: Errors['criteria'];
sourceId: SharedProps['sourceId'];
updateCriteria: (criteria: AlertParams['criteria']) => void;
interface CountCriteriaProps extends SharedProps {
criteria: PartialCountCriteriaType;
}

const CountCriteria: React.FC<CountCriteriaProps> = (props) => {
const { criteria, updateCriteria, errors } = props;

const handleUpdateCriteria = useCallback(
(criteriaParam: CriteriaType) => {
updateCriteria(criteriaParam);
},
[updateCriteria]
);
const { criteria, defaultCriterion, updateCriteria, errors } = props;

const { updateCriterion, addCriterion, removeCriterion } = useCriteriaState(
criteria,
handleUpdateCriteria
defaultCriterion,
updateCriteria
);

return (
Expand All @@ -227,8 +209,9 @@ const CountCriteria: React.FC<CountCriteriaProps> = (props) => {
};

const useCriteriaState = (
criteria: CriteriaType,
onUpdateCriteria: (criteria: CriteriaType) => void
criteria: PartialCountCriteriaType,
defaultCriterion: PartialCriterionType,
onUpdateCriteria: (criteria: PartialCountCriteriaType) => void
) => {
const updateCriterion = useCallback(
(idx, criterionParams) => {
Expand All @@ -241,13 +224,13 @@ const useCriteriaState = (
);

const addCriterion = useCallback(() => {
const nextCriteria = criteria ? [...criteria, DEFAULT_CRITERIA] : [DEFAULT_CRITERIA];
const nextCriteria = [...criteria, defaultCriterion];
onUpdateCriteria(nextCriteria);
}, [criteria, onUpdateCriteria]);
}, [criteria, defaultCriterion, onUpdateCriteria]);

const removeCriterion = useCallback(
(idx) => {
const nextCriteria = criteria.filter((criterion, index) => {
const nextCriteria = criteria.filter((_criterion, index) => {
return index !== idx;
});
onUpdateCriteria(nextCriteria);
Expand Down
Loading

0 comments on commit c2d1b2c

Please sign in to comment.