import {Fragment} from 'react'; import {useTheme} from '@emotion/react'; import ActionLink from 'sentry/components/actions/actionLink'; import ArchiveActions from 'sentry/components/actions/archive'; import IgnoreActions from 'sentry/components/actions/ignore'; import {Button} from 'sentry/components/button'; import {openConfirmModal} from 'sentry/components/confirm'; import {DropdownMenu, MenuItemProps} from 'sentry/components/dropdownMenu'; import {IconEllipsis} from 'sentry/icons'; import {t} from 'sentry/locale'; import GroupStore from 'sentry/stores/groupStore'; import {BaseGroup, GroupStatus, Project} from 'sentry/types'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; import {IssueTypeConfig} from 'sentry/utils/issueTypeConfig/types'; import Projects from 'sentry/utils/projects'; import useMedia from 'sentry/utils/useMedia'; import useOrganization from 'sentry/utils/useOrganization'; import ResolveActions from './resolveActions'; import ReviewAction from './reviewAction'; import {ConfirmAction, getConfirm, getLabel} from './utils'; type Props = { allInQuerySelected: boolean; anySelected: boolean; issues: Set; multiSelected: boolean; onDelete: () => void; onMerge: () => void; onShouldConfirm: (action: ConfirmAction) => boolean; onUpdate: (data?: any) => void; query: string; queryCount: number; selectedProjectSlug?: string; }; function ActionSet({ queryCount, query, allInQuerySelected, anySelected, multiSelected, issues, onUpdate, onShouldConfirm, onDelete, onMerge, selectedProjectSlug, }: Props) { const organization = useOrganization(); const numIssues = issues.size; const confirm = getConfirm({ numIssues, allInQuerySelected, query, queryCount, organization, }); const label = getLabel(numIssues, allInQuerySelected); const selectedIssues = [...issues] .map(issueId => GroupStore.get(issueId)) .filter(issue => issue) as BaseGroup[]; // Merges require multiple issues of a single project type const multipleIssueProjectsSelected = multiSelected && !selectedProjectSlug; const {enabled: mergeSupported, disabledReason: mergeDisabledReason} = isActionSupported(selectedIssues, 'merge'); const {enabled: deleteSupported, disabledReason: deleteDisabledReason} = isActionSupported(selectedIssues, 'delete'); const mergeDisabled = !multiSelected || multipleIssueProjectsSelected || !mergeSupported; const ignoreDisabled = !anySelected; const canMarkReviewed = anySelected && (allInQuerySelected || selectedIssues.some(issue => !!issue?.inbox)); // determine which ... dropdown options to show based on issue(s) selected const canAddBookmark = allInQuerySelected || selectedIssues.some(issue => !issue.isBookmarked); const canRemoveBookmark = allInQuerySelected || selectedIssues.some(issue => issue.isBookmarked); const canSetUnresolved = allInQuerySelected || selectedIssues.some( issue => issue.status === 'resolved' || issue.status === 'ignored' ); const makeMergeTooltip = () => { if (mergeDisabledReason) { return mergeDisabledReason; } if (multipleIssueProjectsSelected) { return t('Cannot merge issues from different projects'); } return ''; }; // Determine whether to nest "Merge" and "Mark as Reviewed" buttons inside // the dropdown menu based on the current screen size const theme = useTheme(); const nestMergeAndReview = useMedia(`(max-width: ${theme.breakpoints.xlarge})`); const hasEscalatingIssuesUi = organization.features.includes('escalating-issues'); const menuItems: MenuItemProps[] = [ { key: 'merge', label: t('Merge'), hidden: !nestMergeAndReview, disabled: mergeDisabled, details: makeMergeTooltip(), onAction: () => { openConfirmModal({ bypass: !onShouldConfirm(ConfirmAction.MERGE), onConfirm: onMerge, message: confirm({action: ConfirmAction.MERGE, canBeUndone: false}), confirmText: label('merge'), }); }, }, { key: 'mark-reviewed', label: t('Mark Reviewed'), hidden: !nestMergeAndReview, disabled: !canMarkReviewed, onAction: () => onUpdate({inbox: false}), }, { key: 'bookmark', label: t('Add to Bookmarks'), hidden: !canAddBookmark, onAction: () => { openConfirmModal({ bypass: !onShouldConfirm(ConfirmAction.BOOKMARK), onConfirm: () => onUpdate({isBookmarked: true}), message: confirm({action: ConfirmAction.BOOKMARK, canBeUndone: false}), confirmText: label('bookmark'), }); }, }, { key: 'remove-bookmark', label: t('Remove from Bookmarks'), hidden: !canRemoveBookmark, onAction: () => { openConfirmModal({ bypass: !onShouldConfirm(ConfirmAction.UNBOOKMARK), onConfirm: () => onUpdate({isBookmarked: false}), message: confirm({ action: ConfirmAction.UNBOOKMARK, canBeUndone: false, append: ' from your bookmarks', }), confirmText: label('remove', ' from your bookmarks'), }); }, }, { key: 'unresolve', label: t('Set status to: Unresolved'), hidden: !canSetUnresolved, onAction: () => { openConfirmModal({ bypass: !onShouldConfirm(ConfirmAction.UNRESOLVE), onConfirm: () => onUpdate({status: GroupStatus.UNRESOLVED}), message: confirm({action: ConfirmAction.UNRESOLVE, canBeUndone: true}), confirmText: label('unresolve'), }); }, }, { key: 'delete', label: t('Delete'), priority: 'danger', disabled: !deleteSupported, details: deleteDisabledReason, onAction: () => { openConfirmModal({ bypass: !onShouldConfirm(ConfirmAction.DELETE), onConfirm: onDelete, priority: 'danger', message: confirm({action: ConfirmAction.DELETE, canBeUndone: false}), confirmText: label('delete'), }); }, }, ]; return ( {hasEscalatingIssuesUi && query.includes('is:archived') ? ( ) : null} {hasEscalatingIssuesUi ? ( confirm({action: ConfirmAction.IGNORE, canBeUndone: true}) } confirmLabel={label('archive')} disabled={ignoreDisabled} /> ) : null} {selectedProjectSlug ? ( {({projects, initiallyLoaded, fetchError}) => { const selectedProject = projects[0]; return ( ); }} ) : ( )} {hasEscalatingIssuesUi ? null : ( confirm({action: ConfirmAction.IGNORE, canBeUndone: true}) } confirmLabel={label('ignore')} disabled={ignoreDisabled} /> )} {!nestMergeAndReview && ( )} {!nestMergeAndReview && ( {t('Merge')} )} , showChevron: false, size: 'xs', }} isDisabled={!anySelected} /> ); } function isActionSupported( selectedIssues: BaseGroup[], actionType: keyof IssueTypeConfig['actions'] ) { for (const issue of selectedIssues) { const info = getConfigForIssueType(issue).actions[actionType]; if (!info.enabled) { return info; } } return {enabled: true}; } export default ActionSet;