Browse Source

feat(roles): Team-roles for Team Settings page (#48197)

http://default.dev.getsentry.net:8000/settings/teams/captain-planet/settings/
Danny Lee 1 year ago
parent
commit
59b08ce993

+ 3 - 5
static/app/data/forms/teamSettingsFields.tsx

@@ -15,12 +15,11 @@ const formGroups: JsonFormObject[] = [
         name: 'slug',
         type: 'string',
         required: true,
-        label: t('Name'),
+        label: t('Team Slug'),
         placeholder: 'e.g. api-team',
         help: t('A unique ID used to identify the team'),
-        disabled: ({access}) => !access.has('team:write'),
         transformInput: slugify,
-
+        disabled: ({hasTeamWrite}) => !hasTeamWrite,
         saveOnBlur: false,
         saveMessageAlertType: 'info',
         saveMessage: t('You will be redirected to the new team slug after saving'),
@@ -43,8 +42,7 @@ const formGroups: JsonFormObject[] = [
         help: t(
           'Organization owners can bulk assign an org-role for all the members in this team'
         ),
-        disabled: ({access, idpProvisioned}) =>
-          !access.has('org:admin') || idpProvisioned,
+        disabled: ({hasOrgAdmin, idpProvisioned}) => !hasOrgAdmin || idpProvisioned,
         visible: ({hasOrgRoleFlag}) => hasOrgRoleFlag,
         saveOnBlur: false,
         saveMessageAlertType: 'info',

+ 6 - 6
static/app/views/settings/organizationTeams/teamSettings/index.spec.jsx

@@ -34,7 +34,8 @@ describe('TeamSettings', function () {
 
     render(<TeamSettings team={team} params={{teamId: team.slug}} />);
 
-    const input = screen.getByRole('textbox', {name: 'Name'});
+    const input = screen.getByRole('textbox', {name: 'Team Slug'});
+
     await userEvent.clear(input);
     await userEvent.type(input, 'NEW SLUG');
 
@@ -56,7 +57,7 @@ describe('TeamSettings', function () {
     );
   });
 
-  it('can set team org role', async function () {
+  it('can set team org-role', async function () {
     const team = TestStubs.Team({orgRole: ''});
     const putMock = MockApiClient.addMockResponse({
       url: `/teams/org-slug/${team.slug}/`,
@@ -85,7 +86,6 @@ describe('TeamSettings', function () {
     await selectEvent.select(unsetDropdown, 'Owner');
 
     await userEvent.click(screen.getByRole('button', {name: 'Save'}));
-
     expect(putMock).toHaveBeenCalledWith(
       `/teams/org-slug/${team.slug}/`,
       expect.objectContaining({
@@ -124,10 +124,10 @@ describe('TeamSettings', function () {
       context,
     });
 
-    expect(screen.getByRole('button', {name: 'Remove Team'})).toBeDisabled();
+    expect(screen.getByTestId('button-remove-team')).toBeDisabled();
   });
 
-  it('needs org:admin in order to set team org role', function () {
+  it('needs org:admin in order to set team org-role', function () {
     const team = TestStubs.Team();
 
     const context = TestStubs.routerContext([
@@ -146,7 +146,7 @@ describe('TeamSettings', function () {
     expect(screen.getByRole('textbox', {name: 'Organization Role'})).toBeDisabled();
   });
 
-  it('cannot set team org role for idp:provisioned team', function () {
+  it('cannot set team org-role for idp:provisioned team', function () {
     const team = TestStubs.Team({flags: {'idp:provisioned': true}});
 
     const context = TestStubs.routerContext([

+ 17 - 7
static/app/views/settings/organizationTeams/teamSettings/index.tsx

@@ -3,6 +3,7 @@ import {browserHistory, RouteComponentProps} from 'react-router';
 
 import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import {removeTeam, updateTeamSuccess} from 'sentry/actionCreators/teams';
+import {hasEveryAccess} from 'sentry/components/acl/access';
 import {Button} from 'sentry/components/button';
 import Confirm from 'sentry/components/confirm';
 import FieldGroup from 'sentry/components/forms/fieldGroup';
@@ -12,10 +13,11 @@ import {Panel, PanelHeader} from 'sentry/components/panels';
 import teamSettingsFields from 'sentry/data/forms/teamSettingsFields';
 import {IconDelete} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
-import {Organization, Scope, Team} from 'sentry/types';
+import {Organization, Team} from 'sentry/types';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import withOrganization from 'sentry/utils/withOrganization';
 import AsyncView from 'sentry/views/asyncView';
+import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
 
 type Props = RouteComponentProps<{teamId: string}, {}> & {
   organization: Organization;
@@ -60,14 +62,17 @@ class TeamSettings extends AsyncView<Props, State> {
 
   renderBody() {
     const {organization, team} = this.props;
-
-    const access = new Set<Scope>(organization.access);
     const idpProvisioned = team.flags['idp:provisioned'];
     const orgRoleList = organization.orgRoleList;
     const hasOrgRoleFlag = organization.features.includes('org-roles-for-teams');
 
+    const hasTeamWrite = hasEveryAccess(['team:write'], {organization, team});
+    const hasOrgAdmin = hasEveryAccess(['org:admin'], {organization, team});
+
     return (
       <Fragment>
+        <PermissionAlert access={['team:write']} team={team} />
+
         <Form
           apiMethod="PUT"
           apiEndpoint={`/teams/${organization.slug}/${team.slug}/`}
@@ -82,8 +87,13 @@ class TeamSettings extends AsyncView<Props, State> {
           }}
         >
           <JsonForm
-            access={access}
-            additionalFieldProps={{idpProvisioned, hasOrgRoleFlag, orgRoleList}}
+            additionalFieldProps={{
+              idpProvisioned,
+              hasOrgRoleFlag,
+              hasTeamWrite,
+              hasOrgAdmin,
+              orgRoleList,
+            }}
             forms={teamSettingsFields}
           />
         </Form>
@@ -97,7 +107,7 @@ class TeamSettings extends AsyncView<Props, State> {
           >
             <div>
               <Confirm
-                disabled={!access.has('team:admin')}
+                disabled={!hasOrgAdmin}
                 onConfirm={this.handleRemoveTeam}
                 priority="danger"
                 message={tct('Are you sure you want to remove the team [team]?', {
@@ -107,7 +117,7 @@ class TeamSettings extends AsyncView<Props, State> {
                 <Button
                   icon={<IconDelete />}
                   priority="danger"
-                  disabled={!access.has('team:admin')}
+                  data-test-id="button-remove-team"
                 >
                   {t('Remove Team')}
                 </Button>