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

[RAM] Add Previous Snooze button #128539

Merged
merged 6 commits into from
Mar 30, 2022
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
Expand Up @@ -22,6 +22,7 @@ describe('RuleStatusDropdown', () => {
enableRule,
snoozeRule,
unsnoozeRule,
previousSnoozeInterval: null,
item: {
id: '1',
name: 'test rule',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
EuiLink,
EuiText,
EuiToolTip,
EuiButtonEmpty,
} from '@elastic/eui';
import { parseInterval } from '../../../../../common';

Expand All @@ -40,17 +41,26 @@ export interface ComponentOpts {
onRuleChanged: () => void;
enableRule: () => Promise<void>;
disableRule: () => Promise<void>;
snoozeRule: (snoozeEndTime: string | -1) => Promise<void>;
snoozeRule: (snoozeEndTime: string | -1, interval: string | null) => Promise<void>;
unsnoozeRule: () => Promise<void>;
previousSnoozeInterval: string | null;
}

const COMMON_SNOOZE_TIMES: Array<[number, SnoozeUnit]> = [
[1, 'h'],
[3, 'h'],
[8, 'h'],
[1, 'd'],
];

export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
item,
onRuleChanged,
disableRule,
enableRule,
snoozeRule,
unsnoozeRule,
previousSnoozeInterval,
}: ComponentOpts) => {
const [isEnabled, setIsEnabled] = useState<boolean>(item.enabled);
const [isSnoozed, setIsSnoozed] = useState<boolean>(isItemSnoozed(item));
Expand All @@ -69,29 +79,35 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
const onChangeEnabledStatus = useCallback(
async (enable: boolean) => {
setIsUpdating(true);
if (enable) {
await enableRule();
} else {
await disableRule();
try {
if (enable) {
await enableRule();
} else {
await disableRule();
}
setIsEnabled(!isEnabled);
onRuleChanged();
} finally {
setIsUpdating(false);
}
setIsEnabled(!isEnabled);
onRuleChanged();
setIsUpdating(false);
},
[setIsUpdating, isEnabled, setIsEnabled, onRuleChanged, enableRule, disableRule]
);
const onChangeSnooze = useCallback(
async (value: number, unit?: SnoozeUnit) => {
setIsUpdating(true);
if (value === -1) {
await snoozeRule(-1);
} else if (value !== 0) {
const snoozeEndTime = moment().add(value, unit).toISOString();
await snoozeRule(snoozeEndTime);
} else await unsnoozeRule();
setIsSnoozed(value !== 0);
onRuleChanged();
setIsUpdating(false);
try {
if (value === -1) {
await snoozeRule(-1, null);
} else if (value !== 0) {
const snoozeEndTime = moment().add(value, unit).toISOString();
await snoozeRule(snoozeEndTime, `${value}${unit}`);
} else await unsnoozeRule();
setIsSnoozed(value !== 0);
onRuleChanged();
} finally {
setIsUpdating(false);
}
},
[setIsUpdating, setIsSnoozed, onRuleChanged, snoozeRule, unsnoozeRule]
);
Expand Down Expand Up @@ -149,6 +165,7 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
isEnabled={isEnabled}
isSnoozed={isSnoozed}
snoozeEndTime={item.snoozeEndTime}
previousSnoozeInterval={previousSnoozeInterval}
/>
</EuiPopover>
</EuiFlexItem>
Expand All @@ -166,6 +183,7 @@ interface RuleStatusMenuProps {
isEnabled: boolean;
isSnoozed: boolean;
snoozeEndTime?: Date | null;
previousSnoozeInterval: string | null;
}

const RuleStatusMenu: React.FunctionComponent<RuleStatusMenuProps> = ({
Expand All @@ -175,6 +193,7 @@ const RuleStatusMenu: React.FunctionComponent<RuleStatusMenuProps> = ({
isEnabled,
isSnoozed,
snoozeEndTime,
previousSnoozeInterval,
}) => {
const enableRule = useCallback(() => {
if (isSnoozed) {
Expand Down Expand Up @@ -242,6 +261,7 @@ const RuleStatusMenu: React.FunctionComponent<RuleStatusMenuProps> = ({
applySnooze={onApplySnooze}
interval={futureTimeToInterval(snoozeEndTime)}
showCancel={isSnoozed}
previousSnoozeInterval={previousSnoozeInterval}
/>
),
},
Expand All @@ -254,12 +274,14 @@ interface SnoozePanelProps {
interval?: string;
applySnooze: (value: number | -1, unit?: SnoozeUnit) => void;
showCancel: boolean;
previousSnoozeInterval: string | null;
}

const SnoozePanel: React.FunctionComponent<SnoozePanelProps> = ({
interval = '3d',
applySnooze,
showCancel,
previousSnoozeInterval,
}) => {
const [intervalValue, setIntervalValue] = useState(parseInterval(interval).value);
const [intervalUnit, setIntervalUnit] = useState(parseInterval(interval).unit);
Expand All @@ -273,17 +295,40 @@ const SnoozePanel: React.FunctionComponent<SnoozePanelProps> = ({
[setIntervalUnit]
);

const onApply1h = useCallback(() => applySnooze(1, 'h'), [applySnooze]);
const onApply3h = useCallback(() => applySnooze(3, 'h'), [applySnooze]);
const onApply8h = useCallback(() => applySnooze(8, 'h'), [applySnooze]);
const onApply1d = useCallback(() => applySnooze(1, 'd'), [applySnooze]);
const onApplyIndefinite = useCallback(() => applySnooze(-1), [applySnooze]);
const onClickApplyButton = useCallback(
() => applySnooze(intervalValue, intervalUnit as SnoozeUnit),
[applySnooze, intervalValue, intervalUnit]
);
const onCancelSnooze = useCallback(() => applySnooze(0, 'm'), [applySnooze]);

const parsedPrevSnooze = previousSnoozeInterval ? parseInterval(previousSnoozeInterval) : null;
const prevSnoozeEqualsCurrentSnooze =
parsedPrevSnooze?.value === intervalValue && parsedPrevSnooze?.unit === intervalUnit;
const previousButton = parsedPrevSnooze && !prevSnoozeEqualsCurrentSnooze && (
<>
<EuiFlexGroup alignItems="center" justifyContent="flexStart" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
style={{ height: '1em' }}
iconType="refresh"
onClick={() => applySnooze(parsedPrevSnooze.value, parsedPrevSnooze.unit as SnoozeUnit)}
>
{i18n.translate('xpack.triggersActionsUI.sections.rulesList.previousSnooze', {
defaultMessage: 'Previous',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="s">
{durationToTextString(parsedPrevSnooze.value, parsedPrevSnooze.unit as SnoozeUnit)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="s" />
</>
);

return (
<EuiPanel paddingSize="none">
<EuiSpacer size="s" />
Expand Down Expand Up @@ -325,6 +370,7 @@ const SnoozePanel: React.FunctionComponent<SnoozePanelProps> = ({
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="s" />
{previousButton}
<EuiFlexGrid columns={2} gutterSize="s">
<EuiFlexItem>
<EuiTitle size="xxs">
Expand All @@ -336,34 +382,13 @@ const SnoozePanel: React.FunctionComponent<SnoozePanelProps> = ({
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem />
<EuiFlexItem>
<EuiLink onClick={onApply1h}>
{i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneHour', {
defaultMessage: '1 hour',
})}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem>
<EuiLink onClick={onApply3h}>
{i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeThreeHours', {
defaultMessage: '3 hours',
})}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem>
<EuiLink onClick={onApply8h}>
{i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeEightHours', {
defaultMessage: '8 hours',
})}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem>
<EuiLink onClick={onApply1d}>
{i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneDay', {
defaultMessage: '1 day',
})}
</EuiLink>
</EuiFlexItem>
{COMMON_SNOOZE_TIMES.map(([value, unit]) => (
<EuiFlexItem key={`snooze-${value}${unit}`}>
<EuiLink onClick={() => applySnooze(value, unit)}>
{durationToTextString(value, unit)}
</EuiLink>
</EuiFlexItem>
))}
</EuiFlexGrid>
<EuiHorizontalRule margin="s" />
<EuiFlexGroup>
Expand Down Expand Up @@ -435,6 +460,15 @@ const futureTimeToInterval = (time?: Date | null) => {
return `${value}${unit}`;
};

const durationToTextString = (value: number, unit: SnoozeUnit) => {
// Moment.humanize will parse "1" as "a" or "an", e.g "an hour"
// Override this to output "1 hour"
if (value === 1) {
return ONE[unit];
}
return moment.duration(value, unit).humanize();
};

const ENABLED = i18n.translate('xpack.triggersActionsUI.sections.rulesList.enabledRuleStatus', {
defaultMessage: 'Enabled',
});
Expand Down Expand Up @@ -478,3 +512,22 @@ const INDEFINITELY = i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.remainingSnoozeIndefinite',
{ defaultMessage: 'Indefinitely' }
);

// i18n constants to override moment.humanize
const ONE: Record<SnoozeUnit, string> = {
m: i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneMinute', {
defaultMessage: '1 minute',
}),
h: i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneHour', {
defaultMessage: '1 hour',
}),
d: i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneDay', {
defaultMessage: '1 day',
}),
w: i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneWeek', {
defaultMessage: '1 week',
}),
M: i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeOneMonth', {
defaultMessage: '1 month',
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export const RulesList: React.FunctionComponent = () => {
const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false);
const [currentRuleToEdit, setCurrentRuleToEdit] = useState<RuleTableItem | null>(null);
const [tagPopoverOpenIndex, setTagPopoverOpenIndex] = useState<number>(-1);
const [previousSnoozeInterval, setPreviousSnoozeInterval] = useState<string | null>(null);
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<Record<string, ReactNode>>(
{}
);
Expand Down Expand Up @@ -352,12 +353,14 @@ export const RulesList: React.FunctionComponent = () => {
<RuleStatusDropdown
disableRule={async () => await disableRule({ http, id: item.id })}
enableRule={async () => await enableRule({ http, id: item.id })}
snoozeRule={async (snoozeEndTime: string | -1) =>
await snoozeRule({ http, id: item.id, snoozeEndTime })
}
snoozeRule={async (snoozeEndTime: string | -1, interval: string | null) => {
await snoozeRule({ http, id: item.id, snoozeEndTime });
setPreviousSnoozeInterval(interval);
}}
unsnoozeRule={async () => await unsnoozeRule({ http, id: item.id })}
item={item}
onRuleChanged={() => loadRulesData()}
previousSnoozeInterval={previousSnoozeInterval}
/>
);
};
Expand Down