Browse Source

feat(stats-detectors): Disable unsupported resolve and archive actions (#60837)

Not all resolve and archive actions are supported for these regression
type issues. Disable them for now.
Tony Xiao 1 year ago
parent
commit
18e3547657

+ 26 - 0
static/app/components/actions/archive.spec.tsx

@@ -77,4 +77,30 @@ describe('ArchiveActions', () => {
       substatus: 'archived_until_condition_met',
     });
   });
+
+  it('does render archive until occurrence options', async () => {
+    render(<ArchiveActions onUpdate={onUpdate} disableArchiveUntilOccurrence={false} />);
+    await userEvent.click(screen.getByRole('button', {name: 'Archive options'}));
+    expect(
+      screen.queryByRole('menuitemradio', {name: 'Until this occurs again\u2026'})
+    ).toBeInTheDocument();
+    expect(
+      screen.queryByRole('menuitemradio', {
+        name: 'Until this affects an additional\u2026',
+      })
+    ).toBeInTheDocument();
+  });
+
+  it('does not render archive until occurrence options', async () => {
+    render(<ArchiveActions onUpdate={onUpdate} disableArchiveUntilOccurrence />);
+    await userEvent.click(screen.getByRole('button', {name: 'Archive options'}));
+    expect(
+      screen.queryByRole('menuitemradio', {name: 'Until this occurs again\u2026'})
+    ).not.toBeInTheDocument();
+    expect(
+      screen.queryByRole('menuitemradio', {
+        name: 'Until this affects an additional\u2026',
+      })
+    ).not.toBeInTheDocument();
+  });
 });

+ 18 - 5
static/app/components/actions/archive.tsx

@@ -15,6 +15,7 @@ interface ArchiveActionProps {
   className?: string;
   confirmLabel?: string;
   confirmMessage?: () => React.ReactNode;
+  disableArchiveUntilOccurrence?: boolean;
   disabled?: boolean;
   isArchived?: boolean;
   shouldConfirm?: boolean;
@@ -32,15 +33,20 @@ const ARCHIVE_FOREVER: GroupStatusResolution = {
   substatus: GroupSubstatus.ARCHIVED_FOREVER,
 };
 
+type GetArchiveActionsProps = Pick<
+  ArchiveActionProps,
+  'shouldConfirm' | 'confirmMessage' | 'onUpdate' | 'confirmLabel'
+> & {
+  disableArchiveUntilOccurrence?: boolean;
+};
+
 export function getArchiveActions({
   shouldConfirm,
   confirmLabel,
   confirmMessage,
   onUpdate,
-}: Pick<
-  ArchiveActionProps,
-  'shouldConfirm' | 'confirmMessage' | 'onUpdate' | 'confirmLabel'
->): {
+  disableArchiveUntilOccurrence,
+}: GetArchiveActionsProps): {
   dropdownItems: MenuItemProps[];
   onArchive: (resolution: GroupStatusResolution) => void;
 } {
@@ -78,7 +84,12 @@ export function getArchiveActions({
         label: t('Forever'),
         onAction: () => onArchive(ARCHIVE_FOREVER),
       },
-      ...dropdownItems,
+      ...dropdownItems.filter(item => {
+        if (disableArchiveUntilOccurrence) {
+          return item.key !== 'until-reoccur' && item.key !== 'until-affect';
+        }
+        return true;
+      }),
     ],
   };
 }
@@ -86,6 +97,7 @@ export function getArchiveActions({
 function ArchiveActions({
   size = 'xs',
   disabled,
+  disableArchiveUntilOccurrence,
   className,
   shouldConfirm,
   confirmLabel,
@@ -116,6 +128,7 @@ function ArchiveActions({
     onUpdate,
     shouldConfirm,
     confirmMessage,
+    disableArchiveUntilOccurrence,
   });
 
   return (

+ 24 - 0
static/app/components/actions/resolve.spec.tsx

@@ -203,4 +203,28 @@ describe('ResolveActions', function () {
       screen.queryByText('Resolving is better with Releases')
     ).not.toBeInTheDocument();
   });
+
+  it('does render more resolve options', function () {
+    render(
+      <ResolveActions
+        onUpdate={spy}
+        hasRelease={false}
+        projectSlug="proj-1"
+        disableResolveInRelease={false}
+      />
+    );
+    expect(screen.getByLabelText('More resolve options')).toBeInTheDocument();
+  });
+
+  it('does not render more resolve options', function () {
+    render(
+      <ResolveActions
+        onUpdate={spy}
+        hasRelease={false}
+        projectSlug="proj-1"
+        disableResolveInRelease
+      />
+    );
+    expect(screen.queryByLabelText('More resolve options')).not.toBeInTheDocument();
+  });
 });

+ 3 - 2
static/app/components/actions/resolve.tsx

@@ -55,6 +55,7 @@ export interface ResolveActionsProps {
   confirmLabel?: string;
   confirmMessage?: React.ReactNode;
   disableDropdown?: boolean;
+  disableResolveInRelease?: boolean;
   disabled?: boolean;
   isAutoResolved?: boolean;
   isResolved?: boolean;
@@ -79,6 +80,7 @@ function ResolveActions({
   shouldConfirm,
   disabled,
   disableDropdown,
+  disableResolveInRelease,
   priority,
   projectFetchError,
   multipleProjectsSelected,
@@ -303,7 +305,7 @@ function ResolveActions({
         >
           {t('Resolve')}
         </ResolveButton>
-        {renderDropdownMenu()}
+        {!disableResolveInRelease && renderDropdownMenu()}
       </ButtonBar>
     </Tooltip>
   );
@@ -313,7 +315,6 @@ export default ResolveActions;
 
 const ResolveButton = styled(Button)<{priority?: 'primary'}>`
   box-shadow: none;
-  border-radius: ${p => p.theme.borderRadiusLeft};
   ${p =>
     p.priority === 'primary' &&
     css`

+ 2 - 0
static/app/utils/issueTypeConfig/cronConfig.tsx

@@ -4,6 +4,7 @@ import {IssueCategoryConfigMapping} from 'sentry/utils/issueTypeConfig/types';
 const cronConfig: IssueCategoryConfigMapping = {
   _categoryDefaults: {
     actions: {
+      archiveUntilOccurrence: {enabled: true},
       delete: {
         enabled: false,
         disabledReason: t('Not yet supported for cron issues'),
@@ -17,6 +18,7 @@ const cronConfig: IssueCategoryConfigMapping = {
         disabledReason: t('Not yet supported for cron issues'),
       },
       ignore: {enabled: true},
+      resolveInRelease: {enabled: true},
       share: {enabled: true},
     },
     attachments: {enabled: false},

+ 2 - 0
static/app/utils/issueTypeConfig/errorConfig.tsx

@@ -3,10 +3,12 @@ import {IssueCategoryConfigMapping} from 'sentry/utils/issueTypeConfig/types';
 const errorConfig: IssueCategoryConfigMapping = {
   _categoryDefaults: {
     actions: {
+      archiveUntilOccurrence: {enabled: true},
       delete: {enabled: true},
       deleteAndDiscard: {enabled: true},
       ignore: {enabled: true},
       merge: {enabled: true},
+      resolveInRelease: {enabled: true},
       share: {enabled: true},
     },
     attachments: {enabled: true},

+ 2 - 0
static/app/utils/issueTypeConfig/index.tsx

@@ -19,10 +19,12 @@ type GetConfigForIssueTypeParams = {eventOccurrenceType: number} | IssueCategory
 
 const BASE_CONFIG: IssueTypeConfig = {
   actions: {
+    archiveUntilOccurrence: {enabled: true},
     delete: {enabled: false},
     deleteAndDiscard: {enabled: false},
     merge: {enabled: false},
     ignore: {enabled: false},
+    resolveInRelease: {enabled: true},
     share: {enabled: false},
   },
   attachments: {enabled: false},

+ 50 - 0
static/app/utils/issueTypeConfig/performanceConfig.tsx

@@ -5,6 +5,7 @@ import {IssueCategoryConfigMapping} from 'sentry/utils/issueTypeConfig/types';
 const performanceConfig: IssueCategoryConfigMapping = {
   _categoryDefaults: {
     actions: {
+      archiveUntilOccurrence: {enabled: true},
       delete: {
         enabled: false,
         disabledReason: t('Not yet supported for performance issues'),
@@ -18,6 +19,7 @@ const performanceConfig: IssueCategoryConfigMapping = {
         disabledReason: t('Not yet supported for performance issues'),
       },
       ignore: {enabled: true},
+      resolveInRelease: {enabled: true},
       share: {enabled: true},
     },
     attachments: {enabled: false},
@@ -195,6 +197,30 @@ const performanceConfig: IssueCategoryConfigMapping = {
     tags: {enabled: false},
   },
   [IssueType.PERFORMANCE_ENDPOINT_REGRESSION]: {
+    actions: {
+      archiveUntilOccurrence: {
+        enabled: false,
+        disabledReason: t('Not yet supported for regression issues'),
+      },
+      delete: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      deleteAndDiscard: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      merge: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      ignore: {enabled: true},
+      resolveInRelease: {
+        enabled: false,
+        disabledReason: t('Not yet supported for regression issues'),
+      },
+      share: {enabled: true},
+    },
     discover: {enabled: false},
     regression: {enabled: true},
     replays: {enabled: false},
@@ -294,6 +320,30 @@ const performanceConfig: IssueCategoryConfigMapping = {
     tags: {enabled: false},
   },
   [IssueType.PROFILE_FUNCTION_REGRESSION]: {
+    actions: {
+      archiveUntilOccurrence: {
+        enabled: false,
+        disabledReason: t('Not yet supported for regression issues'),
+      },
+      delete: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      deleteAndDiscard: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      merge: {
+        enabled: false,
+        disabledReason: t('Not yet supported for performance issues'),
+      },
+      ignore: {enabled: true},
+      resolveInRelease: {
+        enabled: false,
+        disabledReason: t('Not yet supported for regression issues'),
+      },
+      share: {enabled: true},
+    },
     discover: {enabled: false},
     events: {enabled: false},
     regression: {enabled: true},

+ 2 - 0
static/app/utils/issueTypeConfig/types.tsx

@@ -15,10 +15,12 @@ export type IssueTypeConfig = {
    * Enable/disable actions for an issue type
    */
   actions: {
+    archiveUntilOccurrence: DisabledWithReasonConfig;
     delete: DisabledWithReasonConfig;
     deleteAndDiscard: DisabledWithReasonConfig;
     ignore: DisabledWithReasonConfig;
     merge: DisabledWithReasonConfig;
+    resolveInRelease: DisabledWithReasonConfig;
     share: DisabledWithReasonConfig;
   };
   /**

+ 13 - 5
static/app/views/issueDetails/actions/index.tsx

@@ -1,4 +1,4 @@
-import {Fragment, MouseEvent} from 'react';
+import {Fragment, MouseEvent, useMemo} from 'react';
 import {browserHistory} from 'react-router';
 import styled from '@emotion/styled';
 import {Query} from 'history';
@@ -94,10 +94,18 @@ export function Actions(props: Props) {
   const hasEscalatingIssues = organization.features.includes('escalating-issues');
   const hasDeleteAccess = organization.access.includes('event:admin');
 
+  const config = useMemo(() => getConfigForIssueType(group), [group]);
+
   const {
-    actions: {delete: deleteCap, deleteAndDiscard: deleteDiscardCap, share: shareCap},
+    actions: {
+      archiveUntilOccurrence: archiveUntilOccurrenceCap,
+      delete: deleteCap,
+      deleteAndDiscard: deleteDiscardCap,
+      share: shareCap,
+      resolveInRelease: resolveInReleaseCap,
+    },
     discover: discoverCap,
-  } = getConfigForIssueType(group);
+  } = config;
 
   const getDiscoverUrl = () => {
     const {title, type, shortId} = group;
@@ -105,8 +113,6 @@ export function Actions(props: Props) {
     const groupIsOccurrenceBacked =
       group.issueCategory === IssueCategory.PERFORMANCE && !!event?.occurrence;
 
-    const config = getConfigForIssueType(group);
-
     const discoverQuery = {
       id: undefined,
       name: title || type,
@@ -526,6 +532,7 @@ export function Actions(props: Props) {
                 isArchived={isIgnored}
                 onUpdate={onUpdate}
                 disabled={disabled}
+                disableArchiveUntilOccurrence={!archiveUntilOccurrenceCap.enabled}
               />
             </GuideAnchor>
           ) : (
@@ -539,6 +546,7 @@ export function Actions(props: Props) {
           )}
           <GuideAnchor target="resolve" position="bottom" offset={20}>
             <ResolveActions
+              disableResolveInRelease={!resolveInReleaseCap.enabled}
               disabled={disabled}
               disableDropdown={disabled}
               hasRelease={hasRelease}