123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- import * as React from 'react';
- import {css} from '@emotion/react';
- import styled from '@emotion/styled';
- import {openModal} from 'app/actionCreators/modal';
- import ActionLink from 'app/components/actions/actionLink';
- import ButtonBar from 'app/components/buttonBar';
- import CustomIgnoreCountModal from 'app/components/customIgnoreCountModal';
- import CustomIgnoreDurationModal from 'app/components/customIgnoreDurationModal';
- import DropdownLink from 'app/components/dropdownLink';
- import Duration from 'app/components/duration';
- import Tooltip from 'app/components/tooltip';
- import {IconChevron, IconMute} from 'app/icons';
- import {t, tn} from 'app/locale';
- import space from 'app/styles/space';
- import {
- ResolutionStatus,
- ResolutionStatusDetails,
- UpdateResolutionStatus,
- } from 'app/types';
- import ActionButton from './button';
- import MenuHeader from './menuHeader';
- const IGNORE_DURATIONS = [30, 120, 360, 60 * 24, 60 * 24 * 7];
- const IGNORE_COUNTS = [1, 10, 100, 1000, 10000, 100000];
- const IGNORE_WINDOWS: [number, string][] = [
- [60, t('per hour')],
- [24 * 60, t('per day')],
- [24 * 7 * 60, t('per week')],
- ];
- type Props = {
- onUpdate: (params: UpdateResolutionStatus) => void;
- disabled?: boolean;
- shouldConfirm?: boolean;
- confirmMessage?: React.ReactNode;
- confirmLabel?: string;
- isIgnored?: boolean;
- };
- const IgnoreActions = ({
- onUpdate,
- disabled,
- shouldConfirm,
- confirmMessage,
- confirmLabel = t('Ignore'),
- isIgnored = false,
- }: Props) => {
- const onIgnore = (statusDetails: ResolutionStatusDetails) => {
- return onUpdate({
- status: ResolutionStatus.IGNORED,
- statusDetails: statusDetails || {},
- });
- };
- const onCustomIgnore = (statusDetails: ResolutionStatusDetails) => {
- onIgnore(statusDetails);
- };
- const actionLinkProps = {
- shouldConfirm,
- title: t('Ignore'),
- message: confirmMessage,
- confirmLabel,
- disabled,
- };
- if (isIgnored) {
- return (
- <Tooltip title={t('Change status to unresolved')}>
- <ActionButton
- priority="primary"
- onClick={() => onUpdate({status: ResolutionStatus.UNRESOLVED})}
- label={t('Unignore')}
- icon={<IconMute size="xs" />}
- />
- </Tooltip>
- );
- }
- const openCustomIgnoreDuration = () =>
- openModal(deps => (
- <CustomIgnoreDurationModal
- {...deps}
- onSelected={details => onCustomIgnore(details)}
- />
- ));
- const openCustomIngoreCount = () =>
- 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"
- windowChoices={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"
- windowChoices={IGNORE_WINDOWS}
- />
- ));
- return (
- <ButtonBar merged>
- <ActionLink
- {...actionLinkProps}
- type="button"
- title={t('Ignore')}
- onAction={() => onUpdate({status: ResolutionStatus.IGNORED})}
- icon={<IconMute size="xs" />}
- >
- {t('Ignore')}
- </ActionLink>
- <StyledDropdownLink
- customTitle={
- <ActionButton
- disabled={disabled}
- icon={<IconChevron direction="down" size="xs" />}
- />
- }
- alwaysRenderMenu
- disabled={disabled}
- >
- <MenuHeader>{t('Ignore')}</MenuHeader>
- <DropdownMenuItem>
- <DropdownLink
- title={
- <ActionSubMenu>
- {t('For\u2026')}
- <SubMenuChevron>
- <IconChevron direction="right" size="xs" />
- </SubMenuChevron>
- </ActionSubMenu>
- }
- caret={false}
- isNestedDropdown
- alwaysRenderMenu
- >
- {IGNORE_DURATIONS.map(duration => (
- <DropdownMenuItem key={duration}>
- <StyledForActionLink
- {...actionLinkProps}
- onAction={() => onIgnore({ignoreDuration: duration})}
- >
- <ActionSubMenu>
- <Duration seconds={duration * 60} />
- </ActionSubMenu>
- </StyledForActionLink>
- </DropdownMenuItem>
- ))}
- <DropdownMenuItem>
- <ActionSubMenu>
- <a onClick={openCustomIgnoreDuration}>{t('Custom')}</a>
- </ActionSubMenu>
- </DropdownMenuItem>
- </DropdownLink>
- </DropdownMenuItem>
- <DropdownMenuItem>
- <DropdownLink
- title={
- <ActionSubMenu>
- {t('Until this occurs again\u2026')}
- <SubMenuChevron>
- <IconChevron direction="right" size="xs" />
- </SubMenuChevron>
- </ActionSubMenu>
- }
- caret={false}
- isNestedDropdown
- alwaysRenderMenu
- >
- {IGNORE_COUNTS.map(count => (
- <DropdownMenuItem key={count}>
- <DropdownLink
- title={
- <ActionSubMenu>
- {count === 1
- ? t('one time\u2026') // This is intentional as unbalanced string formatters are problematic
- : tn('%s time\u2026', '%s times\u2026', count)}
- <SubMenuChevron>
- <IconChevron direction="right" size="xs" />
- </SubMenuChevron>
- </ActionSubMenu>
- }
- caret={false}
- isNestedDropdown
- alwaysRenderMenu
- >
- <DropdownMenuItem>
- <StyledActionLink
- {...actionLinkProps}
- onAction={() => onIgnore({ignoreCount: count})}
- >
- {t('from now')}
- </StyledActionLink>
- </DropdownMenuItem>
- {IGNORE_WINDOWS.map(([hours, label]) => (
- <DropdownMenuItem key={hours}>
- <StyledActionLink
- {...actionLinkProps}
- onAction={() =>
- onIgnore({
- ignoreCount: count,
- ignoreWindow: hours,
- })
- }
- >
- {label}
- </StyledActionLink>
- </DropdownMenuItem>
- ))}
- </DropdownLink>
- </DropdownMenuItem>
- ))}
- <DropdownMenuItem>
- <ActionSubMenu>
- <a onClick={openCustomIngoreCount}>{t('Custom')}</a>
- </ActionSubMenu>
- </DropdownMenuItem>
- </DropdownLink>
- </DropdownMenuItem>
- <DropdownMenuItem>
- <DropdownLink
- title={
- <ActionSubMenu>
- {t('Until this affects an additional\u2026')}
- <SubMenuChevron>
- <IconChevron direction="right" size="xs" />
- </SubMenuChevron>
- </ActionSubMenu>
- }
- caret={false}
- isNestedDropdown
- alwaysRenderMenu
- >
- {IGNORE_COUNTS.map(count => (
- <DropdownMenuItem key={count}>
- <DropdownLink
- title={
- <ActionSubMenu>
- {tn('one user\u2026', '%s users\u2026', count)}
- <SubMenuChevron>
- <IconChevron direction="right" size="xs" />
- </SubMenuChevron>
- </ActionSubMenu>
- }
- caret={false}
- isNestedDropdown
- alwaysRenderMenu
- >
- <DropdownMenuItem>
- <StyledActionLink
- {...actionLinkProps}
- onAction={() => onIgnore({ignoreUserCount: count})}
- >
- {t('from now')}
- </StyledActionLink>
- </DropdownMenuItem>
- {IGNORE_WINDOWS.map(([hours, label]) => (
- <DropdownMenuItem key={hours}>
- <StyledActionLink
- {...actionLinkProps}
- onAction={() =>
- onIgnore({
- ignoreUserCount: count,
- ignoreUserWindow: hours,
- })
- }
- >
- {label}
- </StyledActionLink>
- </DropdownMenuItem>
- ))}
- </DropdownLink>
- </DropdownMenuItem>
- ))}
- <DropdownMenuItem>
- <ActionSubMenu>
- <a onClick={openCustomIgnoreUserCount}>{t('Custom')}</a>
- </ActionSubMenu>
- </DropdownMenuItem>
- </DropdownLink>
- </DropdownMenuItem>
- </StyledDropdownLink>
- </ButtonBar>
- );
- };
- export default IgnoreActions;
- const actionLinkCss = p => css`
- color: ${p.theme.subText};
- &:hover {
- border-radius: ${p.theme.borderRadius};
- background: ${p.theme.bodyBackground} !important;
- }
- `;
- const StyledActionLink = styled(ActionLink)`
- padding: 7px 10px !important;
- ${actionLinkCss};
- `;
- const StyledForActionLink = styled(ActionLink)`
- padding: ${space(0.5)} 0;
- ${actionLinkCss};
- `;
- const StyledDropdownLink = styled(DropdownLink)`
- transition: none;
- border-top-left-radius: 0 !important;
- border-bottom-left-radius: 0 !important;
- `;
- const DropdownMenuItem = styled('li')`
- :not(:last-child) {
- border-bottom: 1px solid ${p => p.theme.innerBorder};
- }
- > span {
- display: block;
- > ul {
- border-radius: ${p => p.theme.borderRadius};
- top: 5px;
- left: 100%;
- margin-top: -5px;
- margin-left: -1px;
- &:after,
- &:before {
- display: none !important;
- }
- }
- }
- &:hover > span {
- background: ${p => p.theme.focus};
- }
- `;
- const ActionSubMenu = styled('span')`
- display: grid;
- grid-template-columns: 200px 1fr;
- grid-column-start: 1;
- grid-column-end: 4;
- gap: ${space(1)};
- padding: ${space(0.5)} 0;
- color: ${p => p.theme.textColor};
- a {
- color: ${p => p.theme.textColor};
- }
- `;
- const SubMenuChevron = styled('span')`
- display: grid;
- align-self: center;
- color: ${p => p.theme.gray300};
- transition: 0.1s color linear;
- &:hover,
- &:active {
- color: ${p => p.theme.subText};
- }
- `;
|