Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 { useMutation, useQueryClient } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../utils/kibana_react';

export function useRunRule() {
const {
http,
notifications: { toasts },
} = useKibana().services;

const queryClient = useQueryClient();

const runRule = useMutation<string, string, { id: string }>(
['runRule'],
({ id }) => {
try {
return http.post(`/internal/alerting/rule/${id}/_run_soon`);
} catch (e) {
throw new Error(`Unable to parse id: ${e}`);
}
},
{
onError: (_err) => {
toasts.addDanger(
i18n.translate(
'xpack.observability.rules.runErrorModal.errorNotification.descriptionText',
{
defaultMessage: 'Failed to schedule rule',
}
)
);
},

onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false });
toasts.addSuccess(
i18n.translate('xpack.observability.rules.run.successNotification.descriptionText', {
defaultMessage: 'Your rule is scheduled to run',
})
);
},
}
);

return runRule;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { useMutation, useQueryClient } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../utils/kibana_react';

export function useUpdateAPIKey() {
const {
http,
notifications: { toasts },
} = useKibana().services;

const queryClient = useQueryClient();

const updateAPIKey = useMutation<string, string, { id: string }>(
['updateAPIKey'],
({ id }) => {
try {
return http.post(`/api/alerting/rule/${id}/_update_api_key`);
} catch (e) {
throw new Error(`Unable to parse id: ${e}`);
}
},
{
onError: (_err) => {
toasts.addDanger(
i18n.translate(
'xpack.observability.rules.updateAPIKey.errorNotification.descriptionText',
{
defaultMessage: 'Failed to update API key for rule',
}
)
);
},

onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false });
toasts.addSuccess(
i18n.translate(
'xpack.observability.rules.updateAPIKey.successNotification.descriptionText',
{
defaultMessage: 'Updated API key for rule',
}
)
);
},
}
);

return updateAPIKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@
*/

import React, { useState } from 'react';
import { css } from '@emotion/react';
import {
EuiButton,
EuiButtonEmpty,
EuiButtonIcon,
EuiContextMenu,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiText,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { noop } from 'lodash';
import { useFetchRule } from '../../../hooks/use_fetch_rule';
import { useKibana } from '../../../utils/kibana_react';
import { useEnableRule } from '../../../hooks/use_enable_rule';
import { useDisableRule } from '../../../hooks/use_disable_rule';
import { useRunRule } from '../../../hooks/use_run_rule';
import { useUpdateAPIKey } from '../../../hooks/use_update_api_key';
interface HeaderActionsProps {
ruleId: string;
isLoading: boolean;
Expand Down Expand Up @@ -48,8 +52,18 @@ export function HeaderActions({
const [snoozeModalOpen, setSnoozeModalOpen] = useState<boolean>(false);
const [isUntrackAlertsModalOpen, setIsUntrackAlertsModalOpen] = useState<boolean>(false);

const { mutateAsync: enableRule } = useEnableRule();
const { mutateAsync: disableRule } = useDisableRule();
const { euiTheme } = useEuiTheme();

const collapsedItemActionsCss = css`
.collapsedItemActions__deleteButton {
color: ${euiTheme.colors.textDanger};
}
`;

const { mutate: enableRule } = useEnableRule();
const { mutate: disableRule } = useDisableRule();
const { mutate: runRule } = useRunRule();
const { mutate: updateAPIKey } = useUpdateAPIKey();

const onDisableModalClose = () => {
setIsUntrackAlertsModalOpen(false);
Expand All @@ -71,6 +85,20 @@ export function HeaderActions({
onDeleteRule();
};

const handleRunRule = () => {
setIsRuleEditPopoverOpen(false);
runRule({
id: ruleId,
});
};

const handleUpdateAPIKey = () => {
setIsRuleEditPopoverOpen(false);
updateAPIKey({
id: ruleId,
});
};

const handleEnableRule = () => {
setIsRuleEditPopoverOpen(false);
enableRule({
Expand All @@ -95,12 +123,73 @@ export function HeaderActions({
return null;
}

const disableRuleOption = {
'data-test-subj': 'disableRuleButton',
onClick: onDisableModalOpen,
name: i18n.translate('xpack.observability.ruleDetails.disableRule', {
defaultMessage: 'Disable',
}),
};

const enableRuleOption = {
'data-test-subj': 'enableRuleButton',
onClick: handleEnableRule,
name: i18n.translate('xpack.observability.ruleDetails.enableRule', {
defaultMessage: 'Enable',
}),
};

const panels = [
{
id: 0,
hasFocus: false,
items: [
...[rule.enabled ? disableRuleOption : enableRuleOption],
{
'data-test-subj': 'runRuleButton',
onClick: handleRunRule,
name: i18n.translate('xpack.observability.ruleDetails.runRule', {
defaultMessage: 'Run',
}),
},
{
'data-test-subj': 'updateAPIKeyButton',
onClick: handleUpdateAPIKey,
name: i18n.translate('xpack.observability.ruleDetails.updateAPIkey', {
defaultMessage: 'Update API key',
}),
},
{
isSeparator: true as const,
},
{
icon: 'pencil',
'data-test-subj': 'editRuleButton',
onClick: handleEditRule,
name: i18n.translate('xpack.observability.ruleDetails.editRule', {
defaultMessage: 'Edit',
}),
},
{
icon: 'trash',
'data-test-subj': 'deleteRuleButton',
className: 'collapsedItemActions__deleteButton',
onClick: handleRemoveRule,
name: i18n.translate('xpack.observability.ruleDetails.deleteRule', {
defaultMessage: 'Delete',
}),
},
],
},
];

return (
<>
<EuiFlexGroup direction="rowReverse" alignItems="flexStart">
<EuiFlexGroup direction="rowReverse" alignItems="center">
<EuiFlexItem>
<EuiPopover
id="contextRuleEditMenu"
panelPaddingSize="none"
isOpen={isRuleEditPopoverOpen}
closePopover={togglePopover}
button={
Expand All @@ -118,76 +207,26 @@ export function HeaderActions({
</EuiButton>
}
>
<EuiFlexGroup direction="column" alignItems="flexStart" gutterSize="s">
<EuiButtonEmpty
data-test-subj="snoozeRuleButton"
size="s"
iconType={!getRuleHelpers(rule).isRuleSnoozed ? 'bellSlash' : 'bell'}
onClick={() => {
setSnoozeModalOpen(true);
}}
>
<EuiText size="s">
{i18n.translate('xpack.observability.ruleDetails.snoozeButton.snoozeSchedule', {
defaultMessage: 'Update snooze schedule',
})}
</EuiText>
</EuiButtonEmpty>
{rule.enabled ? (
<EuiButtonEmpty
data-test-subj="disableRuleButton"
size="s"
iconType="pause"
onClick={onDisableModalOpen}
>
<EuiText size="s">
{i18n.translate('xpack.observability.ruleDetails.disableRule', {
defaultMessage: 'Disable',
})}
</EuiText>
</EuiButtonEmpty>
) : (
<EuiButtonEmpty
data-test-subj="enableRuleButton"
size="s"
iconType="play"
onClick={handleEnableRule}
>
<EuiText size="s">
{i18n.translate('xpack.observability.ruleDetails.enableRule', {
defaultMessage: 'Enable',
})}
</EuiText>
</EuiButtonEmpty>
)}
<EuiButtonEmpty
data-test-subj="editRuleButton"
size="s"
iconType="pencil"
onClick={handleEditRule}
>
<EuiText size="s">
{i18n.translate('xpack.observability.ruleDetails.editRule', {
defaultMessage: 'Edit rule',
})}
</EuiText>
</EuiButtonEmpty>
<EuiButtonEmpty
size="s"
iconType="trash"
color="danger"
onClick={handleRemoveRule}
data-test-subj="deleteRuleButton"
>
<EuiText size="s">
{i18n.translate('xpack.observability.ruleDetails.deleteRule', {
defaultMessage: 'Delete rule',
})}
</EuiText>
</EuiButtonEmpty>
</EuiFlexGroup>
<EuiContextMenu
initialPanelId={0}
panels={panels}
className="actDetailsCollapsedItemActions"
data-test-subj="detailsCollapsedActionPanel"
data-testid="detailsCollapsedActionPanel"
css={collapsedItemActionsCss}
/>
</EuiPopover>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<EuiButtonIcon
className="snoozeButton"
data-test-subj="snoozeRuleButton"
iconType={getRuleHelpers(rule).isRuleSnoozed ? 'bellSlash' : 'bell'}
onClick={() => {
setSnoozeModalOpen(true);
}}
/>
</EuiFlexItem>
</EuiFlexGroup>

{snoozeModalOpen && (
Expand All @@ -197,9 +236,7 @@ export function HeaderActions({
setSnoozeModalOpen(false);
setIsRuleEditPopoverOpen(false);
}}
onRuleChanged={async () => {
refetch();
}}
onRuleChanged={refetch}
onLoading={noop}
/>
)}
Expand Down