123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- import styled from '@emotion/styled';
- import {openModal} from 'sentry/actionCreators/modal';
- import Button from 'sentry/components/button';
- import ButtonBar from 'sentry/components/buttonBar';
- import {openConfirmModal} from 'sentry/components/confirm';
- import CustomIgnoreCountModal from 'sentry/components/customIgnoreCountModal';
- import CustomIgnoreDurationModal from 'sentry/components/customIgnoreDurationModal';
- import DropdownMenuControl from 'sentry/components/dropdownMenuControl';
- import type {MenuItemProps} from 'sentry/components/dropdownMenuItem';
- import Tooltip from 'sentry/components/tooltip';
- import {IconChevron, IconMute} from 'sentry/icons';
- import {t, tn} from 'sentry/locale';
- import {
- GroupStatusResolution,
- ResolutionStatus,
- ResolutionStatusDetails,
- SelectValue,
- } from 'sentry/types';
- import {getDuration} from 'sentry/utils/formatters';
- const ONE_HOUR = 60;
- /**
- * Ignore durations are in munutes
- */
- const IGNORE_DURATIONS = [
- ONE_HOUR / 2,
- ONE_HOUR * 2,
- ONE_HOUR * 6,
- ONE_HOUR * 24,
- ONE_HOUR * 24 * 7,
- ];
- const IGNORE_COUNTS = [1, 10, 100, 1000, 10000, 100000];
- const IGNORE_WINDOWS: SelectValue<number>[] = [
- {value: ONE_HOUR, label: t('per hour')},
- {value: ONE_HOUR * 24, label: t('per day')},
- {value: ONE_HOUR * 24 * 7, label: t('per week')},
- ];
- /**
- * Create the dropdown submenus
- */
- export function getIgnoreActions({
- confirmLabel,
- confirmMessage,
- shouldConfirm,
- onUpdate,
- }: Pick<
- IgnoreActionProps,
- 'shouldConfirm' | 'confirmMessage' | 'confirmLabel' | 'onUpdate'
- >) {
- const onIgnore = (
- statusDetails: ResolutionStatusDetails | undefined = {},
- {bypassConfirm} = {bypassConfirm: false}
- ) => {
- openConfirmModal({
- bypass: bypassConfirm || !shouldConfirm,
- onConfirm: () =>
- onUpdate({
- status: ResolutionStatus.IGNORED,
- statusDetails,
- }),
- message: confirmMessage?.(statusDetails) ?? null,
- confirmText: confirmLabel,
- });
- };
- const onCustomIgnore = (statusDetails: ResolutionStatusDetails) => {
- onIgnore(statusDetails, {bypassConfirm: true});
- };
- const openCustomIgnoreDuration = () =>
- openModal(deps => (
- <CustomIgnoreDurationModal
- {...deps}
- onSelected={details => onCustomIgnore(details)}
- />
- ));
- const openCustomIgnoreCount = () =>
- openModal(deps => (
- <CustomIgnoreCountModal
- {...deps}
- onSelected={details => onCustomIgnore(details)}
- label={t('Ignore this issue until it occurs again\u2026')}
- countLabel={t('Number of times')}
- countName="ignoreCount"
- windowName="ignoreWindow"
- windowOptions={IGNORE_WINDOWS}
- />
- ));
- const openCustomIgnoreUserCount = () =>
- openModal(deps => (
- <CustomIgnoreCountModal
- {...deps}
- onSelected={details => onCustomIgnore(details)}
- label={t('Ignore this issue until it affects an additional\u2026')}
- countLabel={t('Number of users')}
- countName="ignoreUserCount"
- windowName="ignoreUserWindow"
- windowOptions={IGNORE_WINDOWS}
- />
- ));
- // Move submenu placement when ignore used in top right menu
- const dropdownItems: MenuItemProps[] = [
- {
- key: 'for',
- label: t('For\u2026'),
- isSubmenu: true,
- children: [
- ...IGNORE_DURATIONS.map(duration => ({
- key: `for-${duration}`,
- label: getDuration(duration * 60),
- onAction: () => onIgnore({ignoreDuration: duration}),
- })),
- {
- key: 'for-custom',
- label: t('Custom'),
- onAction: () => openCustomIgnoreDuration(),
- },
- ],
- },
- {
- key: 'until-reoccur',
- label: t('Until this occurs again\u2026'),
- isSubmenu: true,
- children: [
- ...IGNORE_COUNTS.map(count => ({
- key: `until-reoccur-${count}-times`,
- label:
- count === 1
- ? t('one time\u2026') // This is intentional as unbalanced string formatters are problematic
- : tn('%s time\u2026', '%s times\u2026', count),
- isSubmenu: true,
- children: [
- {
- key: `until-reoccur-${count}-times-from-now`,
- label: t('from now'),
- onAction: () => onIgnore({ignoreCount: count}),
- },
- ...IGNORE_WINDOWS.map(({value, label}) => ({
- key: `until-reoccur-${count}-times-from-${label}`,
- label,
- onAction: () =>
- onIgnore({
- ignoreCount: count,
- ignoreWindow: value,
- }),
- })),
- ],
- })),
- {
- key: 'until-reoccur-custom',
- label: t('Custom'),
- onAction: () => openCustomIgnoreCount(),
- },
- ],
- },
- {
- key: 'until-affect',
- label: t('Until this affects an additional\u2026'),
- isSubmenu: true,
- children: [
- ...IGNORE_COUNTS.map(count => ({
- key: `until-affect-${count}-users`,
- label:
- count === 1
- ? t('one user\u2026') // This is intentional as unbalanced string formatters are problematic
- : tn('%s user\u2026', '%s users\u2026', count),
- isSubmenu: true,
- children: [
- {
- key: `until-affect-${count}-users-from-now`,
- label: t('from now'),
- onAction: () => onIgnore({ignoreUserCount: count}),
- },
- ...IGNORE_WINDOWS.map(({value, label}) => ({
- key: `until-affect-${count}-users-from-${label}`,
- label,
- onAction: () =>
- onIgnore({
- ignoreUserCount: count,
- ignoreUserWindow: value,
- }),
- })),
- ],
- })),
- {
- key: 'until-affect-custom',
- label: t('Custom'),
- onAction: () => openCustomIgnoreUserCount(),
- },
- ],
- },
- ];
- return {dropdownItems, onIgnore};
- }
- type IgnoreActionProps = {
- onUpdate: (params: GroupStatusResolution) => void;
- className?: string;
- confirmLabel?: string;
- confirmMessage?: (
- statusDetails: ResolutionStatusDetails | undefined
- ) => React.ReactNode;
- disableTooltip?: boolean;
- disabled?: boolean;
- hideIcon?: boolean;
- isIgnored?: boolean;
- shouldConfirm?: boolean;
- size?: 'xs' | 'sm';
- };
- const IgnoreActions = ({
- onUpdate,
- disabled,
- shouldConfirm,
- confirmMessage,
- className,
- hideIcon,
- disableTooltip,
- size = 'xs',
- confirmLabel = t('Ignore'),
- isIgnored = false,
- }: IgnoreActionProps) => {
- if (isIgnored) {
- return (
- <Tooltip title={t('Change status to unresolved')}>
- <Button
- priority="primary"
- size="xs"
- onClick={() =>
- onUpdate({status: ResolutionStatus.UNRESOLVED, statusDetails: {}})
- }
- aria-label={t('Unignore')}
- icon={<IconMute size="xs" />}
- />
- </Tooltip>
- );
- }
- const {dropdownItems, onIgnore} = getIgnoreActions({
- confirmLabel,
- onUpdate,
- shouldConfirm,
- confirmMessage,
- });
- return (
- <ButtonBar className={className} merged>
- <IgnoreButton
- size={size}
- tooltipProps={{delay: 300, disabled: disabled || disableTooltip}}
- title={t(
- 'Silences alerts for this issue and removes it from the issue stream by default.'
- )}
- icon={hideIcon ? null : <IconMute size={size} />}
- onClick={() => onIgnore()}
- disabled={disabled}
- >
- {t('Ignore')}
- </IgnoreButton>
- <DropdownMenuControl
- size="sm"
- trigger={triggerProps => (
- <DropdownTrigger
- {...triggerProps}
- aria-label={t('Ignore options')}
- size={size}
- icon={<IconChevron direction="down" size="xs" />}
- disabled={disabled}
- />
- )}
- menuTitle={t('Ignore')}
- items={dropdownItems}
- isDisabled={disabled}
- />
- </ButtonBar>
- );
- };
- export default IgnoreActions;
- const IgnoreButton = styled(Button)`
- box-shadow: none;
- border-radius: ${p => p.theme.borderRadiusLeft};
- `;
- const DropdownTrigger = styled(Button)`
- box-shadow: none;
- border-radius: ${p => p.theme.borderRadiusRight};
- border-left: none;
- `;
|