Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Transform] Add alerting rules management to Transform UI #115363

Merged
merged 15 commits into from
Oct 19, 2021
4 changes: 2 additions & 2 deletions x-pack/plugins/transform/common/api_schemas/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common';
import type { Dictionary } from '../types/common';
import type { PivotAggDict } from '../types/pivot_aggs';
import type { PivotGroupByDict } from '../types/pivot_group_by';
import type { TransformId, TransformPivotConfig } from '../types/transform';
import type { TransformId, TransformConfigUnion } from '../types/transform';

import { transformStateSchema, runtimeMappingsSchema } from './common';

Expand All @@ -33,7 +33,7 @@ export type GetTransformsRequestSchema = TypeOf<typeof getTransformsRequestSchem

export interface GetTransformsResponseSchema {
count: number;
transforms: TransformPivotConfig[];
transforms: TransformConfigUnion[];
}

// schemas shared by parts of the preview, create and update endpoint
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/transform/common/types/alerting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import type { AlertTypeParams } from '../../../alerting/common';
import type { Alert, AlertTypeParams } from '../../../alerting/common';

export type TransformHealthRuleParams = {
includeTransforms?: string[];
Expand All @@ -20,3 +20,5 @@ export type TransformHealthRuleParams = {
export type TransformHealthRuleTestsConfig = TransformHealthRuleParams['testsConfig'];

export type TransformHealthTests = keyof Exclude<TransformHealthRuleTestsConfig, null | undefined>;

export type TransformHealthAlertRule = Omit<Alert<TransformHealthRuleParams>, 'apiKey'>;
17 changes: 14 additions & 3 deletions x-pack/plugins/transform/common/types/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* 2.0.
*/

import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
import type { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
import type { LatestFunctionConfig, PutTransformsRequestSchema } from '../api_schemas/transforms';
import { isPopulatedObject } from '../shared_imports';
import { PivotGroupByDict } from './pivot_group_by';
import { PivotAggDict } from './pivot_aggs';
import type { PivotGroupByDict } from './pivot_group_by';
import type { PivotAggDict } from './pivot_aggs';
import type { TransformHealthAlertRule } from './alerting';

export type IndexName = string;
export type IndexPattern = string;
Expand All @@ -22,6 +23,7 @@ export type TransformBaseConfig = PutTransformsRequestSchema & {
id: TransformId;
create_time?: number;
version?: string;
alerting_rules?: TransformHealthAlertRule[];
};

export interface PivotConfigDefinition {
Expand All @@ -45,6 +47,11 @@ export type TransformLatestConfig = Omit<TransformBaseConfig, 'pivot'> & {

export type TransformConfigUnion = TransformPivotConfig | TransformLatestConfig;

export type ContinuousTransform = Omit<TransformConfigUnion, 'sync'> &
Required<{
sync: TransformConfigUnion['sync'];
}>;

export function isPivotTransform(transform: unknown): transform is TransformPivotConfig {
return isPopulatedObject(transform, ['pivot']);
}
Expand All @@ -53,6 +60,10 @@ export function isLatestTransform(transform: unknown): transform is TransformLat
return isPopulatedObject(transform, ['latest']);
}

export function isContinuousTransform(transform: unknown): transform is ContinuousTransform {
return isPopulatedObject(transform, ['sync']);
}

export interface LatestFunctionConfigUI {
unique_key: Array<EuiComboBoxOptionOption<string>> | undefined;
sort: EuiComboBoxOptionOption<string> | undefined;
Expand Down
127 changes: 127 additions & 0 deletions x-pack/plugins/transform/public/alerting/transform_alerting_flyout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { createContext, FC, useContext, useMemo } from 'react';
import { memoize } from 'lodash';
import { BehaviorSubject, Observable } from 'rxjs';
import { pluck } from 'rxjs/operators';
import useObservable from 'react-use/lib/useObservable';
import { useAppDependencies } from '../app/app_dependencies';
import { TransformHealthAlertRule, TransformHealthRuleParams } from '../../common/types/alerting';
import { TRANSFORM_RULE_TYPE } from '../../common';

interface TransformAlertFlyoutProps {
initialAlert?: TransformHealthAlertRule | null;
ruleParams?: TransformHealthRuleParams | null;
onSave?: () => void;
onCloseFlyout: () => void;
}

export const TransformAlertFlyout: FC<TransformAlertFlyoutProps> = ({
initialAlert,
ruleParams,
onCloseFlyout,
onSave,
}) => {
const { triggersActionsUi } = useAppDependencies();

const AlertFlyout = useMemo(() => {
if (!triggersActionsUi) return;

const commonProps = {
onClose: () => {
onCloseFlyout();
},
onSave: async () => {
if (onSave) {
onSave();
}
},
};

if (initialAlert) {
return triggersActionsUi.getEditAlertFlyout({
...commonProps,
initialAlert,
});
}

return triggersActionsUi.getAddAlertFlyout({
...commonProps,
consumer: 'stackAlerts',
canChangeTrigger: false,
alertTypeId: TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH,
metadata: {},
initialValues: {
params: ruleParams!,
},
});
// deps on id to avoid re-rendering on auto-refresh
}, [triggersActionsUi, initialAlert, ruleParams, onCloseFlyout, onSave]);

return <>{AlertFlyout}</>;
};

interface AlertRulesManage {
editAlertRule$: Observable<TransformHealthAlertRule | null>;
createAlertRule$: Observable<TransformHealthRuleParams | null>;
setEditAlertRule: (alertRule: TransformHealthAlertRule) => void;
setCreateAlertRule: (transformId: string) => void;
hideAlertFlyout: () => void;
}

export const getAlertRuleManageContext = memoize(function (): AlertRulesManage {
const ruleState$ = new BehaviorSubject<{
editAlertRule: null | TransformHealthAlertRule;
createAlertRule: null | TransformHealthRuleParams;
}>({
editAlertRule: null,
createAlertRule: null,
});
return {
editAlertRule$: ruleState$.pipe(pluck('editAlertRule')),
createAlertRule$: ruleState$.pipe(pluck('createAlertRule')),
setEditAlertRule: (initialRule) => {
ruleState$.next({
createAlertRule: null,
editAlertRule: initialRule,
});
},
setCreateAlertRule: (transformId: string) => {
ruleState$.next({
createAlertRule: { includeTransforms: [transformId] },
editAlertRule: null,
});
},
hideAlertFlyout: () => {
ruleState$.next({
createAlertRule: null,
editAlertRule: null,
});
},
};
});

export const AlertRulesManageContext = createContext<AlertRulesManage>(getAlertRuleManageContext());

export function useAlertRuleFlyout(): AlertRulesManage {
return useContext(AlertRulesManageContext);
}

export const TransformAlertFlyoutWrapper = () => {
const { editAlertRule$, createAlertRule$, hideAlertFlyout } = useAlertRuleFlyout();
const editAlertRule = useObservable(editAlertRule$);
const createAlertRule = useObservable(createAlertRule$);

return editAlertRule || createAlertRule ? (
<TransformAlertFlyout
initialAlert={editAlertRule}
ruleParams={createAlertRule!}
onCloseFlyout={hideAlertFlyout}
/>
) : null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import type { AppDependencies } from '../app_dependencies';
import { MlSharedContext } from './shared_context';
import type { GetMlSharedImportsReturnType } from '../../shared_imports';
import type { TriggersAndActionsUIPublicPluginStart } from '../../../../triggers_actions_ui/public';

const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
Expand All @@ -43,6 +44,7 @@ const appDependencies: AppDependencies = {
savedObjectsPlugin: savedObjectsPluginMock.createStartContract(),
share: { urlGenerators: { getUrlGenerator: jest.fn() } } as unknown as SharePluginStart,
ml: {} as GetMlSharedImportsReturnType,
triggersActionsUi: {} as jest.Mocked<TriggersAndActionsUIPublicPluginStart>,
};

export const useAppDependencies = () => {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/transform/public/app/app_dependencies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import type { Storage } from '../../../../../src/plugins/kibana_utils/public';

import type { GetMlSharedImportsReturnType } from '../shared_imports';
import type { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';

export interface AppDependencies {
application: CoreStart['application'];
Expand All @@ -34,6 +35,7 @@ export interface AppDependencies {
share: SharePluginStart;
ml: GetMlSharedImportsReturnType;
spaces?: SpacesPluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
}

export const useAppDependencies = () => {
Expand Down
9 changes: 5 additions & 4 deletions x-pack/plugins/transform/public/app/common/transform_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 2.0.
*/

import { EuiTableActionsColumnType } from '@elastic/eui';

import { TransformConfigUnion, TransformId } from '../../../common/types/transform';
import { TransformStats } from '../../../common/types/transform_stats';
import type { EuiTableActionsColumnType } from '@elastic/eui';
import type { TransformConfigUnion, TransformId } from '../../../common/types/transform';
import type { TransformStats } from '../../../common/types/transform_stats';
import type { TransformHealthAlertRule } from '../../../common/types/alerting';

// Used to pass on attribute names to table columns
export enum TRANSFORM_LIST_COLUMN {
Expand All @@ -21,6 +21,7 @@ export interface TransformListRow {
config: TransformConfigUnion;
mode?: string; // added property on client side to allow filtering by this field
stats: TransformStats;
alerting_rules?: TransformHealthAlertRule[];
}

// The single Action type is not exported as is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const useGetTransforms = (
mode:
typeof config.sync !== 'undefined' ? TRANSFORM_MODE.CONTINUOUS : TRANSFORM_MODE.BATCH,
stats,
alerting_rules: config.alerting_rules,
});
return reducedtableRows;
}, [] as TransformListRow[]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function mountManagementSection(
const startServices = await getStartServices();
const [core, plugins] = startServices;
const { application, chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core;
const { data, share, spaces } = plugins;
const { data, share, spaces, triggersActionsUi } = plugins;
const { docTitle } = chrome;

// Initialize services
Expand All @@ -55,6 +55,7 @@ export async function mountManagementSection(
share,
spaces,
ml: await getMlSharedImports(),
triggersActionsUi,
};

const unmountAppCallback = renderApp(element, appDependencies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '@elastic/eui';

import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
import { TransformPivotConfig } from '../../../../common/types/transform';
import { TransformConfigUnion } from '../../../../common/types/transform';

import { isHttpFetchError } from '../../common/request';
import { useApi } from '../../hooks/use_api';
Expand Down Expand Up @@ -50,7 +50,7 @@ export const CloneTransformSection: FC<Props> = ({ match, location }) => {

const transformId = match.params.transformId;

const [transformConfig, setTransformConfig] = useState<TransformPivotConfig>();
const [transformConfig, setTransformConfig] = useState<TransformConfigUnion>();
const [errorMessage, setErrorMessage] = useState<string>();
const [isInitialized, setIsInitialized] = useState(false);
const { error: searchItemsError, searchItems, setSavedObjectId } = useSearchItems(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
EuiText,
} from '@elastic/eui';

import { FormattedMessage } from '@kbn/i18n/react';
import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';

import {
Expand Down Expand Up @@ -52,7 +53,8 @@ import {
} from '../../../../../../common/api_schemas/transforms';
import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common';
import { isPopulatedObject } from '../../../../../../common/shared_imports';
import { isLatestTransform } from '../../../../../../common/types/transform';
import { isContinuousTransform, isLatestTransform } from '../../../../../../common/types/transform';
import { TransformAlertFlyout } from '../../../../../alerting/transform_alerting_flyout';

export interface StepDetailsExposedState {
created: boolean;
Expand Down Expand Up @@ -86,6 +88,7 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
const [loading, setLoading] = useState(false);
const [created, setCreated] = useState(defaults.created);
const [started, setStarted] = useState(defaults.started);
const [alertFlyoutVisible, setAlertFlyoutVisible] = useState(false);
const [indexPatternId, setIndexPatternId] = useState(defaults.indexPatternId);
const [progressPercentComplete, setProgressPercentComplete] = useState<undefined | number>(
undefined
Expand Down Expand Up @@ -398,6 +401,31 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
</EuiFlexItem>
</EuiFlexGroup>
)}
{isContinuousTransform(transformConfig) && created ? (
<EuiFlexGroup alignItems="center" style={FLEX_GROUP_STYLE}>
<EuiFlexItem grow={false} style={FLEX_ITEM_STYLE}>
<EuiButton
fill
isDisabled={loading}
onClick={setAlertFlyoutVisible.bind(null, true)}
data-test-subj="transformWizardCreateAlertButton"
>
<FormattedMessage
id="xpack.transform.stepCreateForm.createAlertRuleButton"
defaultMessage="Create alert rule"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued" size="s">
{i18n.translate('xpack.transform.stepCreateForm.createAlertRuleDescription', {
defaultMessage:
'Opens a wizard to create an alert rule for monitoring transform health.',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
) : null}
<EuiFlexGroup alignItems="center" style={FLEX_GROUP_STYLE}>
<EuiFlexItem grow={false} style={FLEX_ITEM_STYLE}>
<EuiButton
Expand All @@ -414,7 +442,7 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
<EuiText color="subdued" size="s">
{i18n.translate('xpack.transform.stepCreateForm.createTransformDescription', {
defaultMessage:
'Create the transform without starting it. You will be able to start the transform later by returning to the transforms list.',
'Creates the transform without starting it. You will be able to start the transform later by returning to the transforms list.',
})}
</EuiText>
</EuiFlexItem>
Expand Down Expand Up @@ -535,6 +563,12 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
</Fragment>
)}
</EuiForm>
{alertFlyoutVisible ? (
<TransformAlertFlyout
ruleParams={{ includeTransforms: [transformId] }}
onCloseFlyout={setAlertFlyoutVisible.bind(null, false)}
/>
) : null}
</div>
);
}
Expand Down
Loading