mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Actionable Observability] Add rule details page (#130330)
* Add rule details page * Fix route * Fix route * Add useBreadcrumbs * Add rule summary * Complete rule data summary * Update styling * Add update rule * Add edit role * Update desgin * Add conditions * Add connectors icons * Fix more button * Remove unused FelxBox * Add fetch alerts * Move to items to components folder * Format dates * Add tabs * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Use the shared getRuleStatusDropdown * Add permissions * Better handling errors * Fix styling * Fix tag style * Add tags component * Use tags component from triggers aciton ui * Add last24hAlerts hook * Fix last 24h alerts count hook * Fix large font * Fix font size * Fix size Actions * Fix fontsize page header * Fix conditions size * Fix text move vertically on small screen * Update style * Update alerts counts style * Cleanup * Add formatter for the interval * Add edit button on the definition section * Add delete modal * Add loader * Fix conditions panctuation * Fix size * Use the healthColor function from rule component * Add loading while deleting a rule * Use connectors name to show actions * Fix type * Fix rule page * Fix types * Use common RULES_PAGE_LINK var * Fix checks * Better error handling * Better i18n * Code review * Fix checks i18n * Use abort signal * Revert signal for loadRule as there is no tests * Fix style * Fixing tests * Reduce bundle size * Fix i18n * Bump limits
This commit is contained in:
parent
ffc515bf63
commit
0248e9357f
18 changed files with 1110 additions and 4 deletions
|
@ -58,7 +58,7 @@ pageLoadAssetSize:
|
|||
telemetry: 51957
|
||||
telemetryManagementSection: 38586
|
||||
transform: 41007
|
||||
triggersActionsUi: 104400
|
||||
triggersActionsUi: 105800 #This is temporary. Check https://github.com/elastic/kibana/pull/130710#issuecomment-1119843458 & https://github.com/elastic/kibana/issues/130728
|
||||
upgradeAssistant: 81241
|
||||
urlForwarding: 32579
|
||||
usageCollection: 39762
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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 { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { AsApiContract } from '@kbn/actions-plugin/common';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants';
|
||||
import { RULE_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
interface UseFetchLast24hAlertsProps {
|
||||
http: HttpSetup;
|
||||
features: string;
|
||||
ruleId: string;
|
||||
}
|
||||
interface FetchLast24hAlerts {
|
||||
isLoadingLast24hAlerts: boolean;
|
||||
last24hAlerts: number;
|
||||
errorLast24hAlerts: string | undefined;
|
||||
}
|
||||
|
||||
export function useFetchLast24hAlerts({ http, features, ruleId }: UseFetchLast24hAlertsProps) {
|
||||
const [last24hAlerts, setLast24hAlerts] = useState<FetchLast24hAlerts>({
|
||||
isLoadingLast24hAlerts: true,
|
||||
last24hAlerts: 0,
|
||||
errorLast24hAlerts: undefined,
|
||||
});
|
||||
const isCancelledRef = useRef(false);
|
||||
const abortCtrlRef = useRef(new AbortController());
|
||||
const fetchLast24hAlerts = useCallback(async () => {
|
||||
isCancelledRef.current = false;
|
||||
abortCtrlRef.current.abort();
|
||||
abortCtrlRef.current = new AbortController();
|
||||
try {
|
||||
if (!features) return;
|
||||
const { index } = await fetchIndexNameAPI({
|
||||
http,
|
||||
features,
|
||||
});
|
||||
const { error, alertsCount } = await fetchLast24hAlertsAPI({
|
||||
http,
|
||||
index,
|
||||
ruleId,
|
||||
signal: abortCtrlRef.current.signal,
|
||||
});
|
||||
if (error) throw error;
|
||||
if (!isCancelledRef.current) {
|
||||
setLast24hAlerts((oldState: FetchLast24hAlerts) => ({
|
||||
...oldState,
|
||||
last24hAlerts: alertsCount,
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCancelledRef.current) {
|
||||
if (error.name !== 'AbortError') {
|
||||
setLast24hAlerts((oldState: FetchLast24hAlerts) => ({
|
||||
...oldState,
|
||||
isLoading: false,
|
||||
errorLast24hAlerts: RULE_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [http, features, ruleId]);
|
||||
useEffect(() => {
|
||||
fetchLast24hAlerts();
|
||||
}, [fetchLast24hAlerts]);
|
||||
|
||||
return last24hAlerts;
|
||||
}
|
||||
|
||||
interface IndexName {
|
||||
index: string;
|
||||
}
|
||||
|
||||
export async function fetchIndexNameAPI({
|
||||
http,
|
||||
features,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
features: string;
|
||||
}): Promise<IndexName> {
|
||||
const res = await http.get<{ index_name: string[] }>(`${BASE_RAC_ALERTS_API_PATH}/index`, {
|
||||
query: { features },
|
||||
});
|
||||
return {
|
||||
index: res.index_name[0],
|
||||
};
|
||||
}
|
||||
export async function fetchLast24hAlertsAPI({
|
||||
http,
|
||||
index,
|
||||
ruleId,
|
||||
signal,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
index: string;
|
||||
ruleId: string;
|
||||
signal: AbortSignal;
|
||||
}): Promise<{
|
||||
error: string | null;
|
||||
alertsCount: number;
|
||||
}> {
|
||||
try {
|
||||
const res = await http.post<AsApiContract<any>>(`${BASE_RAC_ALERTS_API_PATH}/find`, {
|
||||
signal,
|
||||
body: JSON.stringify({
|
||||
index,
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.uuid': ruleId,
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-24h',
|
||||
lt: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
alerts_count: {
|
||||
cardinality: {
|
||||
field: 'kibana.alert.uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
return {
|
||||
error: null,
|
||||
alertsCount: res.aggregations.alerts_count.value,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error,
|
||||
alertsCount: 0,
|
||||
};
|
||||
}
|
||||
}
|
46
x-pack/plugins/observability/public/hooks/use_fetch_rule.ts
Normal file
46
x-pack/plugins/observability/public/hooks/use_fetch_rule.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { useEffect, useState, useCallback } from 'react';
|
||||
import { loadRule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FetchRuleProps, FetchRule } from '../pages/rule_details/types';
|
||||
import { RULE_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
export function useFetchRule({ ruleId, http }: FetchRuleProps) {
|
||||
const [ruleSummary, setRuleSummary] = useState<FetchRule>({
|
||||
isRuleLoading: true,
|
||||
rule: undefined,
|
||||
errorRule: undefined,
|
||||
});
|
||||
const fetchRuleSummary = useCallback(async () => {
|
||||
try {
|
||||
const rule = await loadRule({
|
||||
http,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
setRuleSummary((oldState: FetchRule) => ({
|
||||
...oldState,
|
||||
isRuleLoading: false,
|
||||
rule,
|
||||
}));
|
||||
} catch (error) {
|
||||
setRuleSummary((oldState: FetchRule) => ({
|
||||
...oldState,
|
||||
isRuleLoading: false,
|
||||
errorRule: RULE_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}, [ruleId, http]);
|
||||
useEffect(() => {
|
||||
fetchRuleSummary();
|
||||
}, [fetchRuleSummary]);
|
||||
|
||||
return { ...ruleSummary, reloadRule: fetchRuleSummary };
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { useEffect, useState, useCallback } from 'react';
|
||||
import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FetchRuleActionsProps } from '../pages/rule_details/types';
|
||||
import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
interface FetchActions {
|
||||
isLoadingActions: boolean;
|
||||
allActions: Array<ActionConnector<Record<string, unknown>>>;
|
||||
errorActions: string | undefined;
|
||||
}
|
||||
|
||||
export function useFetchRuleActions({ http }: FetchRuleActionsProps) {
|
||||
const [ruleActions, setRuleActions] = useState<FetchActions>({
|
||||
isLoadingActions: true,
|
||||
allActions: [] as Array<ActionConnector<Record<string, unknown>>>,
|
||||
errorActions: undefined,
|
||||
});
|
||||
|
||||
const fetchRuleActions = useCallback(async () => {
|
||||
try {
|
||||
const response = await loadAllActions({
|
||||
http,
|
||||
});
|
||||
setRuleActions((oldState: FetchActions) => ({
|
||||
...oldState,
|
||||
isLoadingActions: false,
|
||||
allActions: response,
|
||||
}));
|
||||
} catch (error) {
|
||||
setRuleActions((oldState: FetchActions) => ({
|
||||
...oldState,
|
||||
isLoadingActions: false,
|
||||
errorActions: ACTIONS_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}, [http]);
|
||||
useEffect(() => {
|
||||
fetchRuleActions();
|
||||
}, [fetchRuleActions]);
|
||||
|
||||
return { ...ruleActions, reloadRuleActions: fetchRuleActions };
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { useEffect, useState, useCallback } from 'react';
|
||||
import { loadRuleSummary } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FetchRuleSummaryProps, FetchRuleSummary } from '../pages/rule_details/types';
|
||||
import { RULE_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
export function useFetchRuleSummary({ ruleId, http }: FetchRuleSummaryProps) {
|
||||
const [ruleSummary, setRuleSummary] = useState<FetchRuleSummary>({
|
||||
isLoadingRuleSummary: true,
|
||||
ruleSummary: undefined,
|
||||
errorRuleSummary: undefined,
|
||||
});
|
||||
|
||||
const fetchRuleSummary = useCallback(async () => {
|
||||
setRuleSummary((oldState: FetchRuleSummary) => ({ ...oldState, isLoading: true }));
|
||||
|
||||
try {
|
||||
const response = await loadRuleSummary({
|
||||
http,
|
||||
ruleId,
|
||||
});
|
||||
setRuleSummary((oldState: FetchRuleSummary) => ({
|
||||
...oldState,
|
||||
isLoading: false,
|
||||
ruleSummary: response,
|
||||
}));
|
||||
} catch (error) {
|
||||
setRuleSummary((oldState: FetchRuleSummary) => ({
|
||||
...oldState,
|
||||
isLoading: false,
|
||||
errorRuleSummary: RULE_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}, [ruleId, http]);
|
||||
useEffect(() => {
|
||||
fetchRuleSummary();
|
||||
}, [fetchRuleSummary]);
|
||||
|
||||
return { ...ruleSummary, reloadRuleSummary: fetchRuleSummary };
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
IconType,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { intersectionBy } from 'lodash';
|
||||
import { ActionsProps } from '../types';
|
||||
import { useFetchRuleActions } from '../../../hooks/use_fetch_rule_actions';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
||||
interface MapActionTypeIcon {
|
||||
[key: string]: string | IconType;
|
||||
}
|
||||
const mapActionTypeIcon: MapActionTypeIcon = {
|
||||
/* TODO: Add the rest of the application logs (SVGs ones) */
|
||||
'.server-log': 'logsApp',
|
||||
'.email': 'email',
|
||||
'.pagerduty': 'apps',
|
||||
'.index': 'indexOpen',
|
||||
'.slack': 'logoSlack',
|
||||
'.webhook': 'logoWebhook',
|
||||
};
|
||||
export function Actions({ ruleActions }: ActionsProps) {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { isLoadingActions, allActions, errorActions } = useFetchRuleActions({ http });
|
||||
if (ruleActions && ruleActions.length <= 0) return <EuiText size="s">0</EuiText>;
|
||||
const actions = intersectionBy(allActions, ruleActions, 'actionTypeId');
|
||||
if (isLoadingActions) return <EuiLoadingSpinner size="s" />;
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
{actions.map((action) => (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon size="m" type={mapActionTypeIcon[action.actionTypeId] ?? 'apps'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ margin: '0px' }}>
|
||||
<EuiText size="s">{action.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
))}
|
||||
{errorActions && toasts.addDanger({ title: errorActions })}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { PageTitle } from './page_title';
|
||||
export { ItemTitleRuleSummary } from './item_title_rule_summary';
|
||||
export { ItemValueRuleSummary } from './item_value_rule_summary';
|
||||
export { Actions } from './actions';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { ItemTitleRuleSummaryProps } from '../types';
|
||||
|
||||
export function ItemTitleRuleSummary({ children }: ItemTitleRuleSummaryProps) {
|
||||
return (
|
||||
<EuiTitle size="xxs">
|
||||
<EuiFlexItem style={{ whiteSpace: 'nowrap' }} grow={1}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { ItemValueRuleSummaryProps } from '../types';
|
||||
|
||||
export function ItemValueRuleSummary({ itemValue, extraSpace = true }: ItemValueRuleSummaryProps) {
|
||||
return (
|
||||
<EuiFlexItem grow={extraSpace ? 3 : 1}>
|
||||
<EuiText size="s">{itemValue}</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { ExperimentalBadge } from '../../../components/shared/experimental_badge';
|
||||
import { PageHeaderProps } from '../types';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { LAST_UPDATED_MESSAGE, CREATED_WORD, BY_WORD, ON_WORD } from '../translations';
|
||||
|
||||
export function PageTitle({ rule }: PageHeaderProps) {
|
||||
const { triggersActionsUi } = useKibana().services;
|
||||
const [isTagsPopoverOpen, setIsTagsPopoverOpen] = useState<boolean>(false);
|
||||
const tagsClicked = () =>
|
||||
setIsTagsPopoverOpen(
|
||||
(oldStateIsTagsPopoverOpen) => rule.tags.length > 0 && !oldStateIsTagsPopoverOpen
|
||||
);
|
||||
const closeTagsPopover = () => setIsTagsPopoverOpen(false);
|
||||
return (
|
||||
<>
|
||||
{rule.name} <ExperimentalBadge />
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem component="span" grow={false}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<b>{LAST_UPDATED_MESSAGE}</b> {BY_WORD} {rule.updatedBy} {ON_WORD}
|
||||
{moment(rule.updatedAt).format('ll')}  
|
||||
<b>{CREATED_WORD}</b> {BY_WORD} {rule.createdBy} {ON_WORD}
|
||||
{moment(rule.createdAt).format('ll')}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{rule.tags.length > 0 &&
|
||||
triggersActionsUi.getRuleTagBadge({
|
||||
isOpen: isTagsPopoverOpen,
|
||||
tags: rule.tags,
|
||||
onClick: () => tagsClicked(),
|
||||
onClose: () => closeTagsPopover(),
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { RuleType, Rule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
type Capabilities = Record<string, any>;
|
||||
|
||||
export type InitialRule = Partial<Rule> &
|
||||
Pick<Rule, 'params' | 'consumer' | 'schedule' | 'actions' | 'tags' | 'notifyWhen'>;
|
||||
|
||||
export function hasAllPrivilege(rule: InitialRule, ruleType?: RuleType): boolean {
|
||||
return ruleType?.authorizedConsumers[rule.consumer]?.all ?? false;
|
||||
}
|
||||
|
||||
export const hasExecuteActionsCapability = (capabilities: Capabilities) =>
|
||||
capabilities?.actions?.execute;
|
||||
|
||||
export const RULES_PAGE_LINK = '/app/observability/alerts/rules';
|
||||
export const ALERT_PAGE_LINK = '/app/observability/alerts';
|
483
x-pack/plugins/observability/public/pages/rule_details/index.tsx
Normal file
483
x-pack/plugins/observability/public/pages/rule_details/index.tsx
Normal file
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* 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, { useState, useEffect, useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonIcon,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
EuiHealth,
|
||||
EuiPopover,
|
||||
EuiHorizontalRule,
|
||||
EuiTabbedContent,
|
||||
EuiEmptyPrompt,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
enableRule,
|
||||
disableRule,
|
||||
snoozeRule,
|
||||
unsnoozeRule,
|
||||
deleteRules,
|
||||
useLoadRuleTypes,
|
||||
RuleType,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
// TODO: use a Delete modal from triggersActionUI when it's sharable
|
||||
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { DeleteModalConfirmation } from '../rules/components/delete_modal_confirmation';
|
||||
import { CenterJustifiedSpinner } from '../rules/components/center_justified_spinner';
|
||||
import { getHealthColor, OBSERVABILITY_SOLUTIONS } from '../rules/config';
|
||||
import {
|
||||
RuleDetailsPathParams,
|
||||
EVENT_ERROR_LOG_TAB,
|
||||
EVENT_LOG_LIST_TAB,
|
||||
ALERT_LIST_TAB,
|
||||
} from './types';
|
||||
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useFetchRule } from '../../hooks/use_fetch_rule';
|
||||
import { RULES_BREADCRUMB_TEXT } from '../rules/translations';
|
||||
import { PageTitle, ItemTitleRuleSummary, ItemValueRuleSummary, Actions } from './components';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useFetchLast24hAlerts } from '../../hooks/use_fetch_last24h_alerts';
|
||||
import { formatInterval } from './utils';
|
||||
import {
|
||||
hasExecuteActionsCapability,
|
||||
hasAllPrivilege,
|
||||
RULES_PAGE_LINK,
|
||||
ALERT_PAGE_LINK,
|
||||
} from './config';
|
||||
|
||||
export function RuleDetailsPage() {
|
||||
const {
|
||||
http,
|
||||
triggersActionsUi: { ruleTypeRegistry, getRuleStatusDropdown, getEditAlertFlyout },
|
||||
application: { capabilities, navigateToUrl },
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const { ruleId } = useParams<RuleDetailsPathParams>();
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
const { isRuleLoading, rule, errorRule, reloadRule } = useFetchRule({ ruleId, http });
|
||||
const { ruleTypes } = useLoadRuleTypes({
|
||||
filteredSolutions: OBSERVABILITY_SOLUTIONS,
|
||||
});
|
||||
|
||||
const [features, setFeatures] = useState<string>('');
|
||||
const [ruleType, setRuleType] = useState<RuleType<string, string>>();
|
||||
const [ruleToDelete, setRuleToDelete] = useState<string[]>([]);
|
||||
const [isPageLoading, setIsPageLoading] = useState(false);
|
||||
const { last24hAlerts } = useFetchLast24hAlerts({
|
||||
http,
|
||||
features,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false);
|
||||
const [isRuleEditPopoverOpen, setIsRuleEditPopoverOpen] = useState(false);
|
||||
|
||||
const handleClosePopover = useCallback(() => setIsRuleEditPopoverOpen(false), []);
|
||||
|
||||
const handleOpenPopover = useCallback(() => setIsRuleEditPopoverOpen(true), []);
|
||||
|
||||
const handleRemoveRule = useCallback(() => {
|
||||
setIsRuleEditPopoverOpen(false);
|
||||
if (rule) setRuleToDelete([rule.id]);
|
||||
}, [rule]);
|
||||
|
||||
const handleEditRule = useCallback(() => {
|
||||
setIsRuleEditPopoverOpen(false);
|
||||
setEditFlyoutVisible(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (ruleTypes.length && rule) {
|
||||
const matchedRuleType = ruleTypes.find((type) => type.id === rule.ruleTypeId);
|
||||
if (rule.consumer === ALERTS_FEATURE_ID && matchedRuleType && matchedRuleType.producer) {
|
||||
setRuleType(matchedRuleType);
|
||||
setFeatures(matchedRuleType.producer);
|
||||
} else setFeatures(rule.consumer);
|
||||
}
|
||||
}, [rule, ruleTypes]);
|
||||
|
||||
useBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
href: http.basePath.prepend(ALERT_PAGE_LINK),
|
||||
},
|
||||
{
|
||||
href: http.basePath.prepend(RULES_PAGE_LINK),
|
||||
text: RULES_BREADCRUMB_TEXT,
|
||||
},
|
||||
{
|
||||
text: rule && rule.name,
|
||||
},
|
||||
]);
|
||||
|
||||
const canExecuteActions = hasExecuteActionsCapability(capabilities);
|
||||
const canSaveRule =
|
||||
rule &&
|
||||
hasAllPrivilege(rule, ruleType) &&
|
||||
// if the rule has actions, can the user save the rule's action params
|
||||
(canExecuteActions || (!canExecuteActions && rule.actions.length === 0));
|
||||
|
||||
const hasEditButton =
|
||||
// can the user save the rule
|
||||
canSaveRule &&
|
||||
// is this rule type editable from within Rules Management
|
||||
(ruleTypeRegistry.has(rule.ruleTypeId)
|
||||
? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext
|
||||
: false);
|
||||
|
||||
const getRuleConditionsWording = () => {
|
||||
const numberOfConditions = rule?.params.criteria ? (rule?.params.criteria as any[]).length : 0;
|
||||
return (
|
||||
<>
|
||||
{numberOfConditions}
|
||||
{i18n.translate('xpack.observability.ruleDetails.conditions', {
|
||||
defaultMessage: 'condition{s}',
|
||||
values: { s: numberOfConditions > 1 ? 's' : '' },
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: EVENT_LOG_LIST_TAB,
|
||||
name: i18n.translate('xpack.observability.ruleDetails.rule.eventLogTabText', {
|
||||
defaultMessage: 'Execution history',
|
||||
}),
|
||||
'data-test-subj': 'eventLogListTab',
|
||||
content: <EuiText>Execution history</EuiText>,
|
||||
},
|
||||
{
|
||||
id: ALERT_LIST_TAB,
|
||||
name: i18n.translate('xpack.observability.ruleDetails.rule.alertsTabText', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
'data-test-subj': 'ruleAlertListTab',
|
||||
content: <EuiText>Alerts</EuiText>,
|
||||
},
|
||||
{
|
||||
id: EVENT_ERROR_LOG_TAB,
|
||||
name: i18n.translate('xpack.observability.ruleDetails.rule.errorLogTabText', {
|
||||
defaultMessage: 'Error log',
|
||||
}),
|
||||
'data-test-subj': 'errorLogTab',
|
||||
content: <EuiText>Error log</EuiText>,
|
||||
},
|
||||
];
|
||||
|
||||
if (isPageLoading || isRuleLoading) return <CenterJustifiedSpinner />;
|
||||
if (!rule || errorRule)
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiEmptyPrompt
|
||||
iconType="alert"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate('xpack.observability.ruleDetails.errorPromptTitle', {
|
||||
defaultMessage: 'Unable to load rule details',
|
||||
})}
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
{i18n.translate('xpack.observability.ruleDetails.errorPromptBody', {
|
||||
defaultMessage: 'There was an error loading the rule details.',
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
pageHeader={{
|
||||
pageTitle: <PageTitle rule={rule} />,
|
||||
bottomBorder: false,
|
||||
rightSideItems: hasEditButton
|
||||
? [
|
||||
<EuiFlexGroup direction="rowReverse" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
id="contextRuleEditMenu"
|
||||
isOpen={isRuleEditPopoverOpen}
|
||||
closePopover={handleClosePopover}
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
display="base"
|
||||
size="m"
|
||||
iconType="boxesHorizontal"
|
||||
aria-label="More"
|
||||
onClick={handleOpenPopover}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup direction="column" alignItems="flexStart">
|
||||
<EuiButtonEmpty size="s" iconType="pencil" onClick={handleEditRule}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.observability.ruleDetails.editRule', {
|
||||
defaultMessage: 'Edit rule',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
onClick={handleRemoveRule}
|
||||
>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.observability.ruleDetails.deleteRule', {
|
||||
defaultMessage: 'Delete rule',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexGroup>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<EuiFlexItem>
|
||||
{i18n.translate('xpack.observability.ruleDetails.triggreAction.status', {
|
||||
defaultMessage: 'Status',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
|
||||
{getRuleStatusDropdown({
|
||||
rule,
|
||||
enableRule: async () => await enableRule({ http, id: rule.id }),
|
||||
disableRule: async () => await disableRule({ http, id: rule.id }),
|
||||
onRuleChanged: () => reloadRule(),
|
||||
isEditable: hasEditButton,
|
||||
snoozeRule: async (snoozeEndTime: string | -1) => {
|
||||
await snoozeRule({ http, id: rule.id, snoozeEndTime });
|
||||
},
|
||||
unsnoozeRule: async () => await unsnoozeRule({ http, id: rule.id }),
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
]
|
||||
: [],
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup wrap={true}>
|
||||
{/* Left side of Rule Summary */}
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel
|
||||
color={getHealthColor(rule.executionStatus.status)}
|
||||
hasBorder={false}
|
||||
paddingSize={'l'}
|
||||
>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<EuiHealth textSize="inherit" color={getHealthColor(rule.executionStatus.status)}>
|
||||
{rule.executionStatus.status.charAt(0).toUpperCase() +
|
||||
rule.executionStatus.status.slice(1)}
|
||||
</EuiHealth>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.lastRun', {
|
||||
defaultMessage: 'Last Run',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
extraSpace={false}
|
||||
itemValue={moment(rule.executionStatus.lastExecutionDate).fromNow()}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
|
||||
<ItemValueRuleSummary
|
||||
extraSpace={false}
|
||||
itemValue={`
|
||||
${String(last24hAlerts)} ${i18n.translate(
|
||||
'xpack.observability.ruleDetails.last24h',
|
||||
{
|
||||
defaultMessage: '(last 24 h)',
|
||||
}
|
||||
)}`}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="l" />
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Right side of Rule Summary */}
|
||||
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiPanel color="subdued" hasBorder={false} paddingSize={'l'}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiTitle size="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.observability.ruleDetails.definition', {
|
||||
defaultMessage: 'Definition',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
{hasEditButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType={'pencil'} onClick={() => setEditFlyoutVisible(true)} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.ruleType', {
|
||||
defaultMessage: 'Rule type',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary itemValue={rule.ruleTypeId} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.description', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
itemValue={ruleTypeRegistry.get(rule.ruleTypeId).description}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.conditionsTitle', {
|
||||
defaultMessage: 'Conditions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
{hasEditButton ? (
|
||||
<EuiButtonEmpty onClick={() => setEditFlyoutVisible(true)}>
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.runsEvery', {
|
||||
defaultMessage: 'Runs every',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
|
||||
<ItemValueRuleSummary itemValue={formatInterval(rule.schedule.interval)} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.notifyWhen', {
|
||||
defaultMessage: 'Notify',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
|
||||
<ItemValueRuleSummary itemValue={String(rule.notifyWhen)} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<Actions ruleActions={rule.actions} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTabbedContent data-test-subj="ruleDetailsTabbedContent" tabs={tabs} />
|
||||
{editFlyoutVisible &&
|
||||
getEditAlertFlyout({
|
||||
initialRule: rule,
|
||||
onClose: () => {
|
||||
setEditFlyoutVisible(false);
|
||||
},
|
||||
onSave: reloadRule,
|
||||
})}
|
||||
<DeleteModalConfirmation
|
||||
onDeleted={async () => {
|
||||
setRuleToDelete([]);
|
||||
navigateToUrl(http.basePath.prepend(RULES_PAGE_LINK));
|
||||
}}
|
||||
onErrors={async () => {
|
||||
setRuleToDelete([]);
|
||||
navigateToUrl(http.basePath.prepend(RULES_PAGE_LINK));
|
||||
}}
|
||||
onCancel={() => {}}
|
||||
apiDeleteCall={deleteRules}
|
||||
idsToDelete={ruleToDelete}
|
||||
singleTitle={rule.name}
|
||||
multipleTitle={rule.name}
|
||||
setIsLoadingState={(isLoading: boolean) => {
|
||||
setIsPageLoading(isLoading);
|
||||
}}
|
||||
/>
|
||||
{errorRule && toasts.addDanger({ title: errorRule })}
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const RULE_LOAD_ERROR = (errorMessage: string) =>
|
||||
i18n.translate('xpack.observability.ruleDetails.ruleLoadError', {
|
||||
defaultMessage: 'Unable to load rule. Reason: {message}',
|
||||
values: { message: errorMessage },
|
||||
});
|
||||
|
||||
export const ACTIONS_LOAD_ERROR = (errorMessage: string) =>
|
||||
i18n.translate('xpack.observability.ruleDetails.connectorsLoadError', {
|
||||
defaultMessage: 'Unable to load rule actions connectors. Reason: {message}',
|
||||
values: { message: errorMessage },
|
||||
});
|
||||
|
||||
export const TAGS_TITLE = i18n.translate('xpack.observability.ruleDetails.tagsTitle', {
|
||||
defaultMessage: 'Tags',
|
||||
});
|
||||
|
||||
export const LAST_UPDATED_MESSAGE = i18n.translate(
|
||||
'xpack.observability.ruleDetails.lastUpdatedMessage',
|
||||
{
|
||||
defaultMessage: 'Last updated',
|
||||
}
|
||||
);
|
||||
|
||||
export const BY_WORD = i18n.translate('xpack.observability.ruleDetails.byWord', {
|
||||
defaultMessage: 'by',
|
||||
});
|
||||
|
||||
export const ON_WORD = i18n.translate('xpack.observability.ruleDetails.onWord', {
|
||||
defaultMessage: 'on',
|
||||
});
|
||||
|
||||
export const CREATED_WORD = i18n.translate('xpack.observability.ruleDetails.createdWord', {
|
||||
defaultMessage: 'Created',
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { HttpSetup } from '@kbn/core/public';
|
||||
import { Rule, RuleSummary, RuleType } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
export interface RuleDetailsPathParams {
|
||||
ruleId: string;
|
||||
}
|
||||
export interface PageHeaderProps {
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
export interface FetchRuleProps {
|
||||
ruleId: string;
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
export interface FetchRule {
|
||||
isRuleLoading: boolean;
|
||||
rule?: Rule;
|
||||
ruleType?: RuleType;
|
||||
errorRule?: string;
|
||||
}
|
||||
|
||||
export interface FetchRuleSummaryProps {
|
||||
ruleId: string;
|
||||
http: HttpSetup;
|
||||
}
|
||||
export interface FetchRuleActionsProps {
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
export interface FetchRuleSummary {
|
||||
isLoadingRuleSummary: boolean;
|
||||
ruleSummary?: RuleSummary;
|
||||
errorRuleSummary?: string;
|
||||
}
|
||||
|
||||
export interface AlertListItemStatus {
|
||||
label: string;
|
||||
healthColor: string;
|
||||
actionGroup?: string;
|
||||
}
|
||||
export interface AlertListItem {
|
||||
alert: string;
|
||||
status: AlertListItemStatus;
|
||||
start?: Date;
|
||||
duration: number;
|
||||
isMuted: boolean;
|
||||
sortPriority: number;
|
||||
}
|
||||
export interface ItemTitleRuleSummaryProps {
|
||||
children: string;
|
||||
}
|
||||
export interface ItemValueRuleSummaryProps {
|
||||
itemValue: string;
|
||||
extraSpace?: boolean;
|
||||
}
|
||||
export interface ActionsProps {
|
||||
ruleActions: any[];
|
||||
}
|
||||
|
||||
export const EVENT_LOG_LIST_TAB = 'rule_event_log_list';
|
||||
export const ALERT_LIST_TAB = 'rule_alert_list';
|
||||
export const EVENT_ERROR_LOG_TAB = 'rule_error_log_list';
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../common';
|
||||
|
||||
export const formatInterval = (ruleInterval: string) => {
|
||||
const interval: string[] | null = ruleInterval.match(/(^\d*)([s|m|h|d])/);
|
||||
if (!interval || interval.length < 3) return ruleInterval;
|
||||
const value: number = +interval[1];
|
||||
const unit = interval[2] as TimeUnitChar;
|
||||
return formatDurationFromTimeUnitChar(value, unit);
|
||||
};
|
|
@ -12,9 +12,7 @@ import { useKibana } from '../../../utils/kibana_react';
|
|||
|
||||
export function Name({ name, rule }: RuleNameProps) {
|
||||
const { http } = useKibana().services;
|
||||
const detailsLink = http.basePath.prepend(
|
||||
`/app/management/insightsAndAlerting/triggersActions/rule/${rule.id}`
|
||||
);
|
||||
const detailsLink = http.basePath.prepend(`/app/observability/alerts/rules/${rule.id}`);
|
||||
const link = (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -17,6 +17,7 @@ import { OverviewPage } from '../pages/overview';
|
|||
import { jsonRt } from './json_rt';
|
||||
import { ObservabilityExploratoryView } from '../components/shared/exploratory_view/obsv_exploratory_view';
|
||||
import { RulesPage } from '../pages/rules';
|
||||
import { RuleDetailsPage } from '../pages/rule_details';
|
||||
import { AlertingPages } from '../config';
|
||||
|
||||
export type RouteParams<T extends keyof typeof routes> = DecodeParams<typeof routes[T]['params']>;
|
||||
|
@ -109,4 +110,11 @@ export const routes = {
|
|||
params: {},
|
||||
exact: true,
|
||||
},
|
||||
'/alerts/rules/:ruleId': {
|
||||
handler: () => {
|
||||
return <RuleDetailsPage />;
|
||||
},
|
||||
params: {},
|
||||
exact: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -31,6 +31,11 @@ export type {
|
|||
RuleTypeParams,
|
||||
AsApiContract,
|
||||
RuleTableItem,
|
||||
AlertsTableProps,
|
||||
AlertsData,
|
||||
BulkActionsObjectProp,
|
||||
RuleSummary,
|
||||
AlertStatus,
|
||||
AlertsTableConfigurationRegistryContract,
|
||||
} from './types';
|
||||
|
||||
|
@ -54,6 +59,8 @@ export { Plugin };
|
|||
export * from './plugin';
|
||||
// TODO remove this import when we expose the Rules tables as a component
|
||||
export { loadRules } from './application/lib/rule_api/rules';
|
||||
export { loadRuleTypes } from './application/lib/rule_api';
|
||||
export { loadRuleSummary } from './application/lib/rule_api/rule_summary';
|
||||
export { deleteRules } from './application/lib/rule_api/delete';
|
||||
export { enableRule } from './application/lib/rule_api/enable';
|
||||
export { disableRule } from './application/lib/rule_api/disable';
|
||||
|
@ -63,6 +70,8 @@ export { snoozeRule } from './application/lib/rule_api/snooze';
|
|||
export { unsnoozeRule } from './application/lib/rule_api/unsnooze';
|
||||
export { loadRuleAggregations, loadRuleTags } from './application/lib/rule_api/aggregate';
|
||||
export { useLoadRuleTypes } from './application/hooks/use_load_rule_types';
|
||||
export { loadRule } from './application/lib/rule_api/get_rule';
|
||||
export { loadAllActions } from './application/lib/action_connector_api';
|
||||
|
||||
export { loadActionTypes } from './application/lib/action_connector_api/connector_types';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue