Browse Source

Athena/fly join team (#55499)

Exact copy of https://github.com/getsentry/sentry/pull/55190 except this
time considering that super users don't have teams and still might have
project access.
Athena Moghaddam 1 year ago
parent
commit
e4f8f536af

+ 52 - 0
static/app/components/noProjectMessage.spec.tsx

@@ -1,5 +1,6 @@
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
+import {Label} from 'sentry/components/editableText';
 import NoProjectMessage from 'sentry/components/noProjectMessage';
 import ConfigStore from 'sentry/stores/configStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
@@ -26,6 +27,41 @@ describe('NoProjectMessage', function () {
     expect(screen.getByText('Remain Calm')).toBeInTheDocument();
   });
 
+  it('shows "Join a Team" when member has no teams', function () {
+    const organization = TestStubs.Organization({
+      slug: 'org-slug',
+      access: ['org:read', 'team:read'],
+    });
+    const childrenMock = jest.fn().mockReturnValue(null);
+    ProjectsStore.loadInitialData([]);
+
+    render(
+      <NoProjectMessage organization={organization}>{childrenMock}</NoProjectMessage>
+    );
+
+    expect(childrenMock).not.toHaveBeenCalled();
+    expect(screen.getByRole('button', {name: 'Join a Team'})).toBeInTheDocument();
+  });
+
+  it('does not show up when user has at least a project and a team', function () {
+    const organization = TestStubs.Organization({slug: 'org-slug'});
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    const project = TestStubs.Project({slug: 'project1', teams: [team]});
+    ProjectsStore.loadInitialData([{...project, hasAccess: true}]);
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
+
+    render(
+      <NoProjectMessage organization={organization}>
+        <Label data-test-id="project-row" isDisabled>
+          Some Project
+        </Label>
+      </NoProjectMessage>
+    );
+
+    expect(screen.getByTestId('project-row')).toBeInTheDocument();
+    expect(screen.queryByText('Remain Calm')).not.toBeInTheDocument();
+  });
+
   it('shows "Create Project" button when there are no projects', function () {
     ProjectsStore.loadInitialData([]);
 
@@ -96,4 +132,20 @@ describe('NoProjectMessage', function () {
       screen.getByText('You need at least one project to use this view')
     ).toBeInTheDocument();
   });
+
+  it('shows projects to superusers if membership is not required', function () {
+    ProjectsStore.loadInitialData([TestStubs.Project({isMember: false})]);
+
+    ConfigStore.set('user', {...ConfigStore.get('user'), isSuperuser: true});
+
+    render(
+      <NoProjectMessage organization={org}>
+        <Label data-test-id="project-row" isDisabled>
+          Some Project
+        </Label>
+      </NoProjectMessage>
+    );
+
+    expect(screen.getByTestId('project-row')).toBeInTheDocument();
+  });
 });

+ 6 - 2
static/app/components/noProjectMessage.tsx

@@ -26,6 +26,7 @@ function NoProjectMessage({
 }: Props) {
   const {projects, initiallyLoaded: projectsLoaded} = useProjects();
   const {teams, initiallyLoaded: teamsLoaded} = useTeams();
+  const isTeamMember = teams.some(team => team.isMember);
 
   const orgSlug = organization.slug;
   const {canCreateProject} = useProjectCreationAccess({organization, teams});
@@ -39,7 +40,10 @@ function NoProjectMessage({
       ? !!projects?.some(p => p.hasAccess)
       : !!projects?.some(p => p.isMember && p.hasAccess);
 
-  if (hasProjectAccess || !projectsLoaded || !teamsLoaded) {
+  if (
+    (isTeamMember || isSuperuser) &&
+    (hasProjectAccess || !projectsLoaded || !teamsLoaded)
+  ) {
     return <Fragment>{children}</Fragment>;
   }
 
@@ -81,7 +85,7 @@ function NoProjectMessage({
         <Layout.Title>{t('Remain Calm')}</Layout.Title>
         <HelpMessage>{t('You need at least one project to use this view')}</HelpMessage>
         <Actions gap={1}>
-          {!orgHasProjects ? (
+          {!orgHasProjects && canCreateProject ? (
             createProjectAction
           ) : (
             <Fragment>

+ 2 - 0
static/app/views/alerts/create.spec.tsx

@@ -76,6 +76,8 @@ describe('ProjectAlertsCreate', function () {
   const createWrapper = (props = {}, location = {}) => {
     const {organization, project, router, routerContext} = initializeOrg(props);
     ProjectsStore.loadInitialData([project]);
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     const params = {orgId: organization.slug, projectId: project.slug};
     const wrapper = render(
       <AlertsContainer>

+ 6 - 0
static/app/views/alerts/index.spec.tsx

@@ -1,8 +1,14 @@
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
+import TeamStore from 'sentry/stores/teamStore';
 import AlertsContainer from 'sentry/views/alerts';
 
 describe('AlertsContainer', function () {
+  beforeEach(() => {
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
+  });
+
   function SubView({hasMetricAlerts}: {hasMetricAlerts?: boolean}) {
     return <div>{hasMetricAlerts ? 'access' : 'no access'}</div>;
   }

+ 2 - 0
static/app/views/alerts/list/incidents/index.spec.tsx

@@ -35,6 +35,8 @@ describe('IncidentsList', () => {
   };
 
   beforeEach(() => {
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     MockApiClient.addMockResponse({
       url: '/organizations/org-slug/incidents/',
       body: [

+ 3 - 0
static/app/views/dashboards/detail.spec.tsx

@@ -12,6 +12,7 @@ import {
 
 import * as modals from 'sentry/actionCreators/modal';
 import ProjectsStore from 'sentry/stores/projectsStore';
+import TeamStore from 'sentry/stores/teamStore';
 import CreateDashboard from 'sentry/views/dashboards/create';
 import * as types from 'sentry/views/dashboards/types';
 import ViewEditDashboard from 'sentry/views/dashboards/view';
@@ -27,6 +28,8 @@ describe('Dashboards > Detail', function () {
     let initialData;
 
     beforeEach(function () {
+      const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+      TeamStore.loadInitialData([{...team, access: ['team:read']}]);
       act(() => ProjectsStore.loadInitialData(projects));
       initialData = initializeOrg({organization});
 

+ 3 - 0
static/app/views/dashboards/manage/index.spec.tsx

@@ -4,6 +4,7 @@ import selectEvent from 'react-select-event';
 import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
 
 import ProjectsStore from 'sentry/stores/projectsStore';
+import TeamStore from 'sentry/stores/teamStore';
 import ManageDashboards from 'sentry/views/dashboards/manage';
 
 const FEATURES = [
@@ -22,6 +23,8 @@ describe('Dashboards > Detail', function () {
     features: FEATURES,
   });
   beforeEach(function () {
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     act(() => ProjectsStore.loadInitialData([TestStubs.Project()]));
 
     MockApiClient.addMockResponse({

+ 3 - 0
static/app/views/discover/eventDetails/index.spec.tsx

@@ -2,6 +2,7 @@ import {initializeOrg} from 'sentry-test/initializeOrg';
 import {act, render, screen} from 'sentry-test/reactTestingLibrary';
 
 import ProjectsStore from 'sentry/stores/projectsStore';
+import TeamStore from 'sentry/stores/teamStore';
 import EventView from 'sentry/utils/discover/eventView';
 import {ALL_VIEWS, DEFAULT_EVENT_VIEW} from 'sentry/views/discover/data';
 import EventDetails from 'sentry/views/discover/eventDetails';
@@ -13,6 +14,8 @@ describe('Discover > EventDetails', function () {
   );
 
   beforeEach(function () {
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     act(() => ProjectsStore.loadInitialData([TestStubs.Project()]));
 
     MockApiClient.addMockResponse({

+ 3 - 0
static/app/views/organizationStats/teamInsights/index.spec.tsx

@@ -1,6 +1,7 @@
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import ProjectsStore from 'sentry/stores/projectsStore';
+import TeamStore from 'sentry/stores/teamStore';
 import TeamInsightsContainer from 'sentry/views/organizationStats/teamInsights';
 
 describe('TeamInsightsContainer', () => {
@@ -22,6 +23,8 @@ describe('TeamInsightsContainer', () => {
   });
   it('allows access for orgs with flag', () => {
     ProjectsStore.loadInitialData([TestStubs.Project()]);
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     const organization = TestStubs.Organization({features: ['team-insights']});
     const context = TestStubs.routerContext([{organization}]);
     render(

+ 3 - 0
static/app/views/projectDetail/index.spec.tsx

@@ -4,6 +4,7 @@ import {textWithMarkupMatcher} from 'sentry-test/utils';
 
 import PageFiltersStore from 'sentry/stores/pageFiltersStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
+import TeamStore from 'sentry/stores/teamStore';
 import ProjectDetails from 'sentry/views/projectDetail/projectDetail';
 
 describe('ProjectDetail', function () {
@@ -13,6 +14,8 @@ describe('ProjectDetail', function () {
   beforeEach(() => {
     PageFiltersStore.reset();
     ProjectsStore.reset();
+    const team = TestStubs.Team({slug: 'team-slug', isMember: true});
+    TeamStore.loadInitialData([{...team, access: ['team:read']}]);
     // eslint-disable-next-line no-console
     jest.spyOn(console, 'error').mockImplementation(jest.fn());
 

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