Browse Source

feat(project-creation): Frontend for disableMemberProjectCreation flag (#62603)

Co-authored-by: Ryan Skonnord <ryan.skonnord@sentry.io>
Co-authored-by: Scott Cooper <scttcper@gmail.com>
Danny Lee 7 months ago
parent
commit
62666a4bd7

+ 5 - 1
static/app/components/noProjectMessage.spec.tsx

@@ -59,7 +59,11 @@ describe('NoProjectMessage', function () {
     // No org-level access
     render(
       <NoProjectMessage
-        organization={OrganizationFixture({access: [], features: ['team-roles']})}
+        organization={OrganizationFixture({
+          access: [],
+          features: ['team-roles'],
+          allowMemberProjectCreation: true,
+        })}
       />
     );
 

+ 4 - 4
static/app/components/noProjectMessage.tsx

@@ -5,7 +5,7 @@ import {Button} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import NoProjectEmptyState from 'sentry/components/illustrations/NoProjectEmptyState';
 import * as Layout from 'sentry/components/layouts/thirds';
-import {useProjectCreationAccess} from 'sentry/components/projects/useProjectCreationAccess';
+import {canCreateProject} from 'sentry/components/projects/canCreateProject';
 import {t} from 'sentry/locale';
 import ConfigStore from 'sentry/stores/configStore';
 import {space} from 'sentry/styles/space';
@@ -26,7 +26,7 @@ function NoProjectMessage({
   const {projects, initiallyLoaded: projectsLoaded} = useProjects();
 
   const orgSlug = organization.slug;
-  const {canCreateProject} = useProjectCreationAccess({organization});
+  const canUserCreateProject = canCreateProject(organization);
   const canJoinTeam = organization.access.includes('team:read');
 
   const {isSuperuser} = ConfigStore.get('user');
@@ -59,11 +59,11 @@ function NoProjectMessage({
   const createProjectAction = (
     <Button
       title={
-        canCreateProject
+        canUserCreateProject
           ? undefined
           : t('You do not have permission to create a project.')
       }
-      disabled={!canCreateProject}
+      disabled={!canUserCreateProject}
       priority={orgHasProjects ? 'default' : 'primary'}
       to={`/organizations/${orgSlug}/projects/new/`}
     >

+ 43 - 0
static/app/components/projects/canCreateProject.spec.tsx

@@ -0,0 +1,43 @@
+import {OrganizationFixture} from 'sentry-fixture/organization';
+
+import {canCreateProject} from './canCreateProject';
+
+describe('ProjectCreationAccess', function () {
+  const organization = OrganizationFixture();
+
+  it('passes project creation eligibility for org-manager', function () {
+    const result = canCreateProject(organization);
+    expect(result).toBeTruthy();
+  });
+
+  it('passes for members if org has team-roles', function () {
+    const experiment_org = OrganizationFixture({
+      access: ['org:read', 'team:read', 'project:read'],
+      features: ['team-roles'],
+      allowMemberProjectCreation: true,
+    });
+
+    const result = canCreateProject(experiment_org);
+    expect(result).toBeTruthy();
+  });
+
+  it('fails for members if org has team-roles but disabled allowMemberProjectCreation', function () {
+    const experiment_org = OrganizationFixture({
+      access: ['org:read', 'team:read', 'project:read'],
+      features: ['team-roles'],
+      allowMemberProjectCreation: false,
+    });
+
+    const result = canCreateProject(experiment_org);
+    expect(result).toBeFalsy();
+  });
+
+  it('fails for members if org does not have team-roles', function () {
+    const no_team_role_org = OrganizationFixture({
+      access: ['org:read', 'team:read', 'project:read'],
+    });
+
+    const result = canCreateProject(no_team_role_org);
+    expect(result).toBeFalsy();
+  });
+});

+ 20 - 0
static/app/components/projects/canCreateProject.tsx

@@ -0,0 +1,20 @@
+import type {Organization} from 'sentry/types/organization';
+
+/**
+ * Used to determine if viewer can see project creation button
+ */
+export function canCreateProject(organization: Organization) {
+  if (organization.access.includes('project:admin')) {
+    return true;
+  }
+
+  // Has member-project-creation feature and didn't disable in org-wide config
+  if (
+    organization.features.includes('team-roles') &&
+    organization.allowMemberProjectCreation
+  ) {
+    return true;
+  }
+
+  return false;
+}

+ 0 - 39
static/app/components/projects/useProjectCreationAccess.spec.tsx

@@ -1,39 +0,0 @@
-import {OrganizationFixture} from 'sentry-fixture/organization';
-
-import {renderHook} from 'sentry-test/reactTestingLibrary';
-
-import {useProjectCreationAccess} from './useProjectCreationAccess';
-
-describe('ProjectCreationAccess', function () {
-  const organization = OrganizationFixture();
-
-  it('passes project creation eligibility for org-manager', function () {
-    const {result} = renderHook(useProjectCreationAccess, {
-      initialProps: {organization},
-    });
-    expect(result.current.canCreateProject).toBeTruthy();
-  });
-
-  it('passes for members if org has team-roles', function () {
-    const experiment_org = OrganizationFixture({
-      access: ['org:read', 'team:read', 'project:read'],
-      features: ['team-roles'],
-    });
-
-    const {result} = renderHook(useProjectCreationAccess, {
-      initialProps: {organization: experiment_org},
-    });
-    expect(result.current.canCreateProject).toBeTruthy();
-  });
-
-  it('fails for members if org does not have team-roles', function () {
-    const no_team_role_org = OrganizationFixture({
-      access: ['org:read', 'team:read', 'project:read'],
-    });
-
-    const {result} = renderHook(useProjectCreationAccess, {
-      initialProps: {organization: no_team_role_org},
-    });
-    expect(result.current.canCreateProject).toBeFalsy();
-  });
-});

+ 0 - 11
static/app/components/projects/useProjectCreationAccess.tsx

@@ -1,11 +0,0 @@
-import type {Organization} from 'sentry/types/organization';
-
-/**
- * Used to determine if viewer can see project creation button
- */
-export function useProjectCreationAccess({organization}: {organization: Organization}) {
-  const canCreateProject =
-    organization.access.includes('project:admin') ||
-    organization.features.includes('team-roles');
-  return {canCreateProject};
-}

+ 6 - 2
static/app/data/forms/organizationGeneralSettings.tsx

@@ -74,7 +74,6 @@ const formGroups: JsonFormObject[] = [
       {
         name: 'defaultRole',
         type: 'select',
-        required: true,
         label: t('Default Role'),
         // seems weird to have choices in initial form data
         choices: ({initialData} = {}) =>
@@ -85,10 +84,15 @@ const formGroups: JsonFormObject[] = [
       {
         name: 'openMembership',
         type: 'boolean',
-        required: true,
         label: t('Open Membership'),
         help: t('Allow organization members to freely join any team'),
       },
+      {
+        name: 'allowMemberProjectCreation',
+        type: 'boolean',
+        label: t('Let Members Create Projects'),
+        help: t('Allow organization members to create and configure new projects.'),
+      },
       {
         name: 'eventsMemberAdmin',
         type: 'boolean',

+ 1 - 0
static/app/types/organization.tsx

@@ -52,6 +52,7 @@ export interface Organization extends OrganizationSummary {
   aggregatedDataConsent: boolean;
   alertsMemberWrite: boolean;
   allowJoinRequests: boolean;
+  allowMemberProjectCreation: boolean;
   allowSharedIssues: boolean;
   attachmentsRole: string;
   /** @deprecated use orgRoleList instead. */

+ 6 - 0
static/app/views/projectInstall/createProject.spec.tsx

@@ -106,6 +106,7 @@ describe('CreateProject', function () {
       organization: {
         access: ['project:read'],
         features: ['team-roles'],
+        allowMemberProjectCreation: true,
       },
     });
 
@@ -152,6 +153,7 @@ describe('CreateProject', function () {
       organization: {
         access: ['project:read'],
         features: ['team-roles'],
+        allowMemberProjectCreation: true,
       },
     });
 
@@ -178,6 +180,7 @@ describe('CreateProject', function () {
       organization: {
         access: ['project:read'],
         features: ['team-roles'],
+        allowMemberProjectCreation: true,
       },
     });
 
@@ -210,6 +213,7 @@ describe('CreateProject', function () {
       organization: {
         access: ['project:read'],
         features: ['team-roles'],
+        allowMemberProjectCreation: true,
       },
     });
 
@@ -250,6 +254,7 @@ describe('CreateProject', function () {
       organization: {
         access: ['project:read'],
         features: ['team-roles'],
+        allowMemberProjectCreation: true,
       },
     });
 
@@ -281,6 +286,7 @@ describe('CreateProject', function () {
       organization: {
         features: ['onboarding-sdk-selection', 'team-roles'],
         access: ['project:read', 'project:write'],
+        allowMemberProjectCreation: true,
       },
     });
 

+ 4 - 4
static/app/views/projectInstall/createProject.tsx

@@ -18,7 +18,7 @@ import ListItem from 'sentry/components/list/listItem';
 import {SupportedLanguages} from 'sentry/components/onboarding/frameworkSuggestionModal';
 import type {Platform} from 'sentry/components/platformPicker';
 import PlatformPicker from 'sentry/components/platformPicker';
-import {useProjectCreationAccess} from 'sentry/components/projects/useProjectCreationAccess';
+import {canCreateProject} from 'sentry/components/projects/canCreateProject';
 import TeamSelector from 'sentry/components/teamSelector';
 import {Tooltip} from 'sentry/components/tooltip';
 import {t, tct} from 'sentry/locale';
@@ -258,7 +258,7 @@ function CreateProject() {
   }
 
   const {shouldCreateCustomRule, conditions} = alertRuleConfig || {};
-  const {canCreateProject} = useProjectCreationAccess({organization});
+  const canUserCreateProject = canCreateProject(organization);
 
   const canCreateTeam = organization.access.includes('project:admin');
   const isOrgMemberWithNoAccess = accessTeams.length === 0 && !canCreateTeam;
@@ -274,7 +274,7 @@ function CreateProject() {
     isMissingAlertThreshold,
   ].filter(value => value).length;
 
-  const canSubmitForm = !inFlight && canCreateProject && formErrorCount === 0;
+  const canSubmitForm = !inFlight && canUserCreateProject && formErrorCount === 0;
 
   let submitTooltipText: string = t('Please select a team');
   if (formErrorCount > 1) {
@@ -318,7 +318,7 @@ function CreateProject() {
   }, [autoFill, gettingStartedWithProjectContext.project?.alertRules]);
 
   return (
-    <Access access={canCreateProject ? ['project:read'] : ['project:admin']}>
+    <Access access={canUserCreateProject ? ['project:read'] : ['project:admin']}>
       <div data-test-id="onboarding-info">
         <List symbol="colored-numeric">
           <Layout.Title withMargins>{t('Create a new project in 3 steps')}</Layout.Title>

Some files were not shown because too many files changed in this diff