Skip to content

Commit

Permalink
added generic action group UI
Browse files Browse the repository at this point in the history
  • Loading branch information
gmmorris committed Nov 12, 2020
1 parent 76be068 commit 74b779a
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 56 deletions.
9 changes: 9 additions & 0 deletions x-pack/examples/alerting_example/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ export const ALERTING_EXAMPLE_APP_ID = 'AlertingExample';

// always firing
export const DEFAULT_INSTANCES_TO_GENERATE = 5;
export interface AlwaysFiringParams {
instances?: number;
thresholds?: {
small?: number;
medium?: number;
large?: number;
};
}
export type AlwaysFiringActionGroupIds = keyof AlwaysFiringParams['thresholds'];

// Astros
export enum Craft {
Expand Down
145 changes: 129 additions & 16 deletions x-pack/examples/alerting_example/public/alert_types/always_firing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFieldNumber, EuiFormRow } from '@elastic/eui';
import React, { Fragment, useState } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiFieldNumber,
EuiFormRow,
EuiPopover,
EuiExpression,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AlertTypeModel } from '../../../../plugins/triggers_actions_ui/public';
import { DEFAULT_INSTANCES_TO_GENERATE } from '../../common/constants';

interface AlwaysFiringParamsProps {
alertParams: { instances?: number };
setAlertParams: (property: string, value: any) => void;
errors: { [key: string]: string[] };
}
import { omit, pick } from 'lodash';
import {
ActionGroupWithCondition,
AlertConditions,
AlertConditionsGroup,
AlertTypeModel,
AlertTypeParamsExpressionProps,
AlertsContextValue,
} from '../../../../plugins/triggers_actions_ui/public';
import {
AlwaysFiringParams,
AlwaysFiringActionGroupIds,
DEFAULT_INSTANCES_TO_GENERATE,
} from '../../common/constants';

export function getAlertType(): AlertTypeModel {
return {
Expand All @@ -24,7 +38,7 @@ export function getAlertType(): AlertTypeModel {
iconClass: 'bolt',
documentationUrl: null,
alertParamsExpression: AlwaysFiringExpression,
validate: (alertParams: AlwaysFiringParamsProps['alertParams']) => {
validate: (alertParams: AlwaysFiringParams) => {
const { instances } = alertParams;
const validationResult = {
errors: {
Expand All @@ -44,11 +58,21 @@ export function getAlertType(): AlertTypeModel {
};
}

export const AlwaysFiringExpression: React.FunctionComponent<AlwaysFiringParamsProps> = ({
alertParams,
setAlertParams,
}) => {
const { instances = DEFAULT_INSTANCES_TO_GENERATE } = alertParams;
const DEFAULT_THRESHOLDS: AlwaysFiringParams['thresholds'] = {
small: 0,
medium: 5000,
large: 10000,
};

export const AlwaysFiringExpression: React.FunctionComponent<AlertTypeParamsExpressionProps<
AlwaysFiringParams,
AlertsContextValue
>> = ({ alertParams, setAlertParams, actionGroups, defaultActionGroupId }) => {
const {
instances = DEFAULT_INSTANCES_TO_GENERATE,
thresholds = pick(DEFAULT_THRESHOLDS, defaultActionGroupId),
} = alertParams;

return (
<Fragment>
<EuiFlexGroup gutterSize="s" wrap direction="column">
Expand All @@ -67,6 +91,95 @@ export const AlwaysFiringExpression: React.FunctionComponent<AlwaysFiringParamsP
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<AlertConditions
headline={'Set different thresholds for randomly generated T-Shirt sizes'}
actionGroups={actionGroups.map((actionGroup) =>
Number.isInteger(thresholds[actionGroup.id as AlwaysFiringActionGroupIds])
? {
...actionGroup,
conditions: thresholds[actionGroup.id as AlwaysFiringActionGroupIds]!,
}
: actionGroup
)}
onInitializeConditionsFor={(actionGroup) => {
setAlertParams('thresholds', {
...thresholds,
...pick(DEFAULT_THRESHOLDS, actionGroup.id),
});
}}
>
<AlertConditionsGroup
onResetConditionsFor={(actionGroup) => {
setAlertParams('thresholds', omit(thresholds, actionGroup.id));
}}
>
<TShirtSelector
setTShirtThreshold={(actionGroup) => {
setAlertParams('thresholds', {
...thresholds,
[actionGroup.id]: actionGroup.conditions,
});
}}
/>
</AlertConditionsGroup>
</AlertConditions>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
</Fragment>
);
};

interface TShirtSelectorProps {
actionGroup?: ActionGroupWithCondition<number>;
setTShirtThreshold: (actionGroup: ActionGroupWithCondition<number>) => void;
}
const TShirtSelector = ({ actionGroup, setTShirtThreshold }: TShirtSelectorProps) => {
const [isOpen, setIsOpen] = useState(false);

if (!actionGroup) {
return null;
}

return (
<EuiPopover
panelPaddingSize="s"
button={
<EuiExpression
description={'Is Above'}
value={actionGroup.conditions}
isActive={isOpen}
onClick={() => setIsOpen(true)}
/>
}
isOpen={isOpen}
closePopover={() => setIsOpen(false)}
ownFocus
anchorPosition="downLeft"
>
<EuiFlexGroup>
<EuiFlexItem grow={false} style={{ width: 150 }}>
{'Is Above'}
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 100 }}>
<EuiFieldNumber
compressed
value={actionGroup.conditions}
onChange={(e) => {
const conditions = parseInt(e.target.value, 10);
if (e.target.value && !isNaN(conditions)) {
setTShirtThreshold({
...actionGroup,
conditions,
});
}
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopover>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,54 @@
import uuid from 'uuid';
import { range, random } from 'lodash';
import { AlertType } from '../../../../plugins/alerts/server';
import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
import {
DEFAULT_INSTANCES_TO_GENERATE,
ALERTING_EXAMPLE_APP_ID,
AlwaysFiringParams,
} from '../../common/constants';

const ACTION_GROUPS = [
{ id: 'small', name: 'small' },
{ id: 'medium', name: 'medium' },
{ id: 'large', name: 'large' },
{ id: 'small', name: 'Small t-shirt' },
{ id: 'medium', name: 'Medium t-shirt' },
{ id: 'large', name: 'Large t-shirt' },
];
const DEFAULT_ACTION_GROUP = 'small';

export const alertType: AlertType = {
function getTShirtSizeByIdAndThreshold(id: string, thresholds: AlwaysFiringParams['thresholds']) {
const idAsNumber = parseInt(id, 10);
if (!isNaN(idAsNumber)) {
if (thresholds?.large && thresholds.large < idAsNumber) {
return 'large';
}
if (thresholds?.medium && thresholds.medium < idAsNumber) {
return 'medium';
}
if (thresholds?.small && thresholds.small < idAsNumber) {
return 'small';
}
}
return DEFAULT_ACTION_GROUP;
}

export const alertType: AlertType<AlwaysFiringParams> = {
id: 'example.always-firing',
name: 'Always firing',
actionGroups: ACTION_GROUPS,
defaultActionGroupId: 'small',
async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) {
defaultActionGroupId: DEFAULT_ACTION_GROUP,
async executor({
services,
params: { instances = DEFAULT_INSTANCES_TO_GENERATE, thresholds },
state,
}) {
const count = (state.count ?? 0) + 1;

range(instances)
.map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! }))
.forEach((instance: { id: string; tshirtSize: string }) => {
.map(() => uuid.v4())
.forEach((id: string) => {
services
.alertInstanceFactory(instance.id)
.alertInstanceFactory(id)
.replaceState({ triggerdOnCycle: count })
.scheduleActions(instance.tshirtSize);
.scheduleActions(getTShirtSizeByIdAndThreshold(id, thresholds));
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import * as React from 'react';
import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
import { ActionGroupWithCondition, AlertConditions } from './alert_conditions';
import AlertConditions, { ActionGroupWithCondition } from './alert_conditions';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiTitle,
EuiDescriptionList,
EuiDescriptionListTitle,
EuiDescriptionListDescription,
EuiButtonEmpty,
} from '@elastic/eui';

describe('alert_conditions', () => {
Expand Down Expand Up @@ -153,6 +154,56 @@ describe('alert_conditions', () => {
expect(wrapper.find(EuiDescriptionList).find(EuiDescriptionListDescription).length).toEqual(2);
});

it('render add buttons for action group without conditions', async () => {
const onInitializeConditionsFor = jest.fn();

const ConditionForm = ({
actionGroup,
}: {
actionGroup?: ActionGroupWithCondition<{ someProp: string }>;
}) => {
return (
<EuiDescriptionList>
<EuiDescriptionListTitle>ID</EuiDescriptionListTitle>
<EuiDescriptionListDescription>{actionGroup?.id}</EuiDescriptionListDescription>
</EuiDescriptionList>
);
};

const wrapper = await setup(
<AlertConditions
actionGroups={[
{
id: 'shouldRender',
name: 'Should Render',
conditions: { someProp: 'shouldRender on a prop' },
},
{
id: 'shouldntRender',
name: 'Should Not Render',
},
]}
onInitializeConditionsFor={onInitializeConditionsFor}
>
<ConditionForm />
</AlertConditions>
);

expect(wrapper.find(EuiButtonEmpty).get(0)).toMatchInlineSnapshot(`
<EuiButtonEmpty
onClick={[Function]}
>
Should Not Render
</EuiButtonEmpty>
`);
wrapper.find(EuiButtonEmpty).simulate('click');

expect(onInitializeConditionsFor).toHaveBeenCalledWith({
id: 'shouldntRender',
name: 'Should Not Render',
});
});

it('passes in any additional props the container passes in', async () => {
const callbackProp = jest.fn();

Expand Down
Loading

0 comments on commit 74b779a

Please sign in to comment.