Skip to content

Commit

Permalink
[Transform] Add alerting rules management to Transform UI (elastic#11…
Browse files Browse the repository at this point in the history
…5363)

* transform alert flyout

* fetch alerting rules

* show alerting rules indicators

* filter continuous transforms

* add alert rules to the expanded row

* edit alert rule from the list

* fix ts issues

* fix types

* update texts

* refactor using context, wip create alert from the list

* update unit test

* fix ts issue

* privilege check
  • Loading branch information
darnautov authored and kibanamachine committed Oct 19, 2021
1 parent 635859f commit 7829853
Show file tree
Hide file tree
Showing 33 changed files with 539 additions and 74 deletions.
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 @@ -20,12 +20,14 @@ interface Authorization {
capabilities: Capabilities;
}

const initialCapabalities: Capabilities = {
const initialCapabilities: Capabilities = {
canGetTransform: false,
canDeleteTransform: false,
canPreviewTransform: false,
canCreateTransform: false,
canStartStopTransform: false,
canCreateTransformAlerts: false,
canUseTransformAlerts: false,
};

const initialValue: Authorization = {
Expand All @@ -35,7 +37,7 @@ const initialValue: Authorization = {
hasAllPrivileges: false,
missingPrivileges: {},
},
capabilities: initialCapabalities,
capabilities: initialCapabilities,
};

export const AuthorizationContext = createContext<Authorization>({ ...initialValue });
Expand All @@ -58,7 +60,7 @@ export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) =
const value = {
isLoading,
privileges: isLoading ? { ...initialValue.privileges } : privilegesData,
capabilities: { ...initialCapabalities },
capabilities: { ...initialCapabilities },
apiError: error ? (error as Error) : null,
};

Expand All @@ -85,6 +87,10 @@ export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) =
hasPrivilege(['cluster', 'cluster:admin/transform/start_task']) &&
hasPrivilege(['cluster', 'cluster:admin/transform/stop']);

value.capabilities.canCreateTransformAlerts = value.capabilities.canCreateTransform;

value.capabilities.canUseTransformAlerts = value.capabilities.canGetTransform;

return (
<AuthorizationContext.Provider value={{ ...value }}>{children}</AuthorizationContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface Capabilities {
canPreviewTransform: boolean;
canCreateTransform: boolean;
canStartStopTransform: boolean;
canCreateTransformAlerts: boolean;
canUseTransformAlerts: boolean;
}

export type Privilege = [string, string];
Expand Down Expand Up @@ -67,6 +69,14 @@ export function createCapabilityFailureMessage(
defaultMessage: 'You do not have permission to create transforms.',
});
break;
case 'canCreateTransformAlerts':
message = i18n.translate(
'xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip',
{
defaultMessage: 'You do not have permission to create transform alert rules.',
}
);
break;
case 'canStartStopTransform':
message = i18n.translate(
'xpack.transform.capability.noPermission.startOrStopTransformTooltip',
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
Loading

0 comments on commit 7829853

Please sign in to comment.