Browse Source

feat(roles): Team-roles for Create Alert Button (#50449)

Allow team-admins to go into Alert Creation flow.
Danny Lee 1 year ago
parent
commit
83312d3f68

+ 75 - 5
static/app/components/createAlertButton.spec.jsx

@@ -41,7 +41,7 @@ describe('CreateAlertFromViewButton', () => {
     expect(onClickMock).toHaveBeenCalledTimes(1);
     expect(onClickMock).toHaveBeenCalledTimes(1);
   });
   });
 
 
-  it('disables the create alert button for members', () => {
+  it('disables the button for org-member', () => {
     const eventView = EventView.fromSavedQuery({
     const eventView = EventView.fromSavedQuery({
       ...DEFAULT_EVENT_VIEW,
       ...DEFAULT_EVENT_VIEW,
     });
     });
@@ -49,13 +49,17 @@ describe('CreateAlertFromViewButton', () => {
       ...organization,
       ...organization,
       access: [],
       access: [],
     };
     };
+    const noAccessProj = {
+      ...TestStubs.Project(),
+      access: [],
+    };
 
 
     render(
     render(
       <CreateAlertFromViewButton
       <CreateAlertFromViewButton
         location={location}
         location={location}
-        organization={organization}
+        organization={noAccessOrg}
         eventView={eventView}
         eventView={eventView}
-        projects={[TestStubs.Project()]}
+        projects={[noAccessProj]}
         onClick={onClickMock}
         onClick={onClickMock}
       />,
       />,
       {
       {
@@ -67,7 +71,73 @@ describe('CreateAlertFromViewButton', () => {
     expect(screen.getByRole('button', {name: 'Create Alert'})).toBeDisabled();
     expect(screen.getByRole('button', {name: 'Create Alert'})).toBeDisabled();
   });
   });
 
 
-  it('shows a guide for members', () => {
+  it('enables the button for org-owner/manager', () => {
+    const eventView = EventView.fromSavedQuery({
+      ...DEFAULT_EVENT_VIEW,
+    });
+    const noAccessProj = {
+      ...TestStubs.Project(),
+      access: [],
+    };
+
+    render(
+      <CreateAlertFromViewButton
+        location={location}
+        organization={organization}
+        eventView={eventView}
+        projects={[noAccessProj]}
+        onClick={onClickMock}
+      />,
+      {
+        context: TestStubs.routerContext([{organization}]),
+        organization,
+      }
+    );
+
+    expect(screen.getByRole('button', {name: 'Create Alert'})).toBeEnabled();
+  });
+
+  it('enables the button for team-admin', () => {
+    const eventView = EventView.fromSavedQuery({
+      ...DEFAULT_EVENT_VIEW,
+    });
+    const noAccessOrg = {
+      ...organization,
+      access: [],
+    };
+    const projects = [
+      {
+        ...TestStubs.Project(),
+        id: 1,
+        slug: 'admin-team',
+        access: ['alerts:write'],
+      },
+      {
+        ...TestStubs.Project(),
+        id: 2,
+        slug: 'contributor-team',
+        access: ['alerts:read'],
+      },
+    ];
+
+    render(
+      <CreateAlertFromViewButton
+        location={location}
+        organization={noAccessOrg}
+        eventView={eventView}
+        projects={projects}
+        onClick={onClickMock}
+      />,
+      {
+        context: TestStubs.routerContext([{organization: noAccessOrg}]),
+        organization: noAccessOrg,
+      }
+    );
+
+    expect(screen.getByRole('button', {name: 'Create Alert'})).toBeEnabled();
+  });
+
+  it('shows a guide for org-member', () => {
     const noAccessOrg = {
     const noAccessOrg = {
       ...organization,
       ...organization,
       access: [],
       access: [],
@@ -80,7 +150,7 @@ describe('CreateAlertFromViewButton', () => {
     expect(GuideStore.state.anchors).toEqual(new Set(['alerts_write_member']));
     expect(GuideStore.state.anchors).toEqual(new Set(['alerts_write_member']));
   });
   });
 
 
-  it('shows a guide for owners/admins', () => {
+  it('shows a guide for org-owner/manager', () => {
     const adminAccessOrg = {
     const adminAccessOrg = {
       ...organization,
       ...organization,
       access: ['org:write'],
       access: ['org:write'],

+ 20 - 22
static/app/components/createAlertButton.tsx

@@ -4,7 +4,7 @@ import {
   addSuccessMessage,
   addSuccessMessage,
 } from 'sentry/actionCreators/indicator';
 } from 'sentry/actionCreators/indicator';
 import {navigateTo} from 'sentry/actionCreators/navigation';
 import {navigateTo} from 'sentry/actionCreators/navigation';
-import Access from 'sentry/components/acl/access';
+import {hasEveryAccess} from 'sentry/components/acl/access';
 import GuideAnchor from 'sentry/components/assistant/guideAnchor';
 import GuideAnchor from 'sentry/components/assistant/guideAnchor';
 import {Button, ButtonProps} from 'sentry/components/button';
 import {Button, ButtonProps} from 'sentry/components/button';
 import Link from 'sentry/components/links/link';
 import Link from 'sentry/components/links/link';
@@ -15,6 +15,7 @@ import type {Organization, Project} from 'sentry/types';
 import type EventView from 'sentry/utils/discover/eventView';
 import type EventView from 'sentry/utils/discover/eventView';
 import useApi from 'sentry/utils/useApi';
 import useApi from 'sentry/utils/useApi';
 import useRouter from 'sentry/utils/useRouter';
 import useRouter from 'sentry/utils/useRouter';
+import withProjects from 'sentry/utils/withProjects';
 import {
 import {
   AlertType,
   AlertType,
   AlertWizardAlertNames,
   AlertWizardAlertNames,
@@ -91,6 +92,7 @@ function CreateAlertFromViewButton({
   return (
   return (
     <CreateAlertButton
     <CreateAlertButton
       organization={organization}
       organization={organization}
+      projects={projects}
       onClick={handleClick}
       onClick={handleClick}
       to={to}
       to={to}
       aria-label={t('Create Alert')}
       aria-label={t('Create Alert')}
@@ -101,6 +103,7 @@ function CreateAlertFromViewButton({
 
 
 type CreateAlertButtonProps = {
 type CreateAlertButtonProps = {
   organization: Organization;
   organization: Organization;
+  projects: Project[];
   alertOption?: keyof typeof AlertWizardAlertNames;
   alertOption?: keyof typeof AlertWizardAlertNames;
   hideIcon?: boolean;
   hideIcon?: boolean;
   iconProps?: SVGIconProps;
   iconProps?: SVGIconProps;
@@ -117,6 +120,7 @@ type CreateAlertButtonProps = {
 
 
 function CreateAlertButton({
 function CreateAlertButton({
   organization,
   organization,
+  projects,
   projectSlug,
   projectSlug,
   iconProps,
   iconProps,
   referrer,
   referrer,
@@ -191,28 +195,22 @@ function CreateAlertButton({
   );
   );
 
 
   const showGuide = !organization.alertsMemberWrite && !!showPermissionGuide;
   const showGuide = !organization.alertsMemberWrite && !!showPermissionGuide;
-
-  return (
-    <Access access={['alerts:write']}>
-      {({hasAccess}) =>
-        showGuide ? (
-          <Access access={['org:write']}>
-            {({hasAccess: isOrgAdmin}) => (
-              <GuideAnchor
-                target={isOrgAdmin ? 'alerts_write_owner' : 'alerts_write_member'}
-                onFinish={isOrgAdmin ? enableAlertsMemberWrite : undefined}
-              >
-                {renderButton(hasAccess)}
-              </GuideAnchor>
-            )}
-          </Access>
-        ) : (
-          renderButton(hasAccess)
-        )
-      }
-    </Access>
+  const canCreateAlert =
+    hasEveryAccess(['alerts:write'], {organization}) ||
+    projects.some(p => hasEveryAccess(['alerts:write'], {project: p}));
+  const hasOrgWrite = hasEveryAccess(['org:write'], {organization});
+
+  return showGuide ? (
+    <GuideAnchor
+      target={hasOrgWrite ? 'alerts_write_owner' : 'alerts_write_member'}
+      onFinish={hasOrgWrite ? enableAlertsMemberWrite : undefined}
+    >
+      {renderButton(canCreateAlert)}
+    </GuideAnchor>
+  ) : (
+    renderButton(canCreateAlert)
   );
   );
 }
 }
 
 
 export {CreateAlertFromViewButton};
 export {CreateAlertFromViewButton};
-export default CreateAlertButton;
+export default withProjects(CreateAlertButton);