Browse Source

feat(roles): Team-roles for Project Settings page (#48182)

http://default.dev.getsentry.net:8000/settings/projects/earth/
Danny Lee 1 year ago
parent
commit
4b5d292c81

+ 4 - 1
src/sentry/api/endpoints/project_details.py

@@ -418,7 +418,10 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
         """
 
         old_data = serialize(project, request.user, DetailedProjectSerializer())
-        has_project_write = request.access and request.access.has_scope("project:write")
+        has_project_write = request.access and (
+            request.access.has_scope("project:write")
+            or request.access.has_project_scope(project, "project:write")
+        )
 
         if has_project_write:
             serializer_cls = ProjectAdminSerializer

+ 11 - 4
static/app/views/settings/projectGeneralSettings/index.tsx

@@ -7,6 +7,7 @@ import {
   removeProject,
   transferProject,
 } from 'sentry/actionCreators/projects';
+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';
@@ -114,7 +115,11 @@ class ProjectGeneralSettings extends AsyncView<Props, State> {
     }
   };
 
-  isProjectAdmin = () => this.props.organization.access.includes('project:admin');
+  isProjectAdmin = () =>
+    hasEveryAccess(['project:admin'], {
+      organization: this.props.organization,
+      project: this.state.data,
+    });
 
   renderRemoveProject() {
     const project = this.state.data;
@@ -245,15 +250,17 @@ class ProjectGeneralSettings extends AsyncView<Props, State> {
     const project = this.state.data;
     const {projectId} = this.props.params;
     const endpoint = `/projects/${organization.slug}/${projectId}/`;
-    const access = new Set(organization.access);
+    const access = new Set(organization.access.concat(project.access));
+
     const jsonFormProps = {
       additionalFieldProps: {
         organization,
       },
       features: new Set(organization.features),
       access,
-      disabled: !access.has('project:write'),
+      disabled: !hasEveryAccess(['project:write'], {organization, project}),
     };
+
     const team = project.teams.length ? project.teams?.[0] : undefined;
 
     /*
@@ -287,7 +294,7 @@ class ProjectGeneralSettings extends AsyncView<Props, State> {
     return (
       <div>
         <SettingsPageHeader title={t('Project Settings')} />
-        <PermissionAlert />
+        <PermissionAlert project={project} />
 
         <Form {...formProps}>
           <JsonForm

+ 26 - 2
tests/sentry/api/endpoints/test_project_details.py

@@ -29,7 +29,7 @@ from sentry.models import (
 )
 from sentry.services.hybrid_cloud.actor import RpcActor
 from sentry.testutils import APITestCase
-from sentry.testutils.helpers import Feature, faux
+from sentry.testutils.helpers import Feature, faux, with_feature
 from sentry.testutils.silo import region_silo_test
 from sentry.types.integrations import ExternalProviders
 from sentry.utils import json
@@ -407,9 +407,33 @@ class ProjectUpdateTest(APITestCase):
         )
 
         assert Project.objects.get(id=project.id).slug != "zzz"
-
         assert not ProjectBookmark.objects.filter(user_id=user.id, project_id=project.id).exists()
 
+    @with_feature("organizations:team-roles")
+    def test_member_with_team_role(self):
+        user = self.create_user("bar@example.com")
+        self.create_member(
+            user=user,
+            organization=self.organization,
+            role="member",
+        )
+
+        team = self.create_team(organization=self.organization)
+        project = self.create_project(teams=[team])
+        self.create_team_membership(user=user, team=team, role="admin")
+
+        self.login_as(user=user)
+
+        self.get_success_response(
+            self.organization.slug,
+            project.slug,
+            slug="zzz",
+            isBookmarked="true",
+        )
+
+        assert Project.objects.get(id=project.id).slug == "zzz"
+        assert ProjectBookmark.objects.filter(user_id=user.id, project_id=project.id).exists()
+
     def test_name(self):
         self.get_success_response(self.org_slug, self.proj_slug, name="hello world")
         project = Project.objects.get(id=self.project.id)