import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; import {RouterContextFixture} from 'sentry-fixture/routerContextFixture'; import {TeamFixture} from 'sentry-fixture/team'; import {TeamAlertsTriggeredFixture} from 'sentry-fixture/teamAlertsTriggered'; import {TeamResolutionTimeFixture} from 'sentry-fixture/teamResolutionTime'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import ProjectsStore from 'sentry/stores/projectsStore'; import TeamStore from 'sentry/stores/teamStore'; import type {Project, Team as TeamType} from 'sentry/types'; import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; import localStorage from 'sentry/utils/localStorage'; import TeamStatsHealth from 'sentry/views/organizationStats/teamInsights/health'; jest.mock('sentry/utils/localStorage'); jest.mock('sentry/utils/isActiveSuperuser', () => ({ isActiveSuperuser: jest.fn(), })); describe('TeamStatsHealth', () => { const project1 = ProjectFixture({id: '2', name: 'js', slug: 'js'}); const project2 = ProjectFixture({id: '3', name: 'py', slug: 'py'}); const team1 = TeamFixture({ id: '2', slug: 'frontend', name: 'frontend', projects: [project1], isMember: true, }); const team2 = TeamFixture({ id: '3', slug: 'backend', name: 'backend', projects: [project2], isMember: true, }); const team3 = TeamFixture({ id: '4', slug: 'internal', name: 'internal', projects: [], isMember: false, }); const {routerProps, router} = initializeOrg(); beforeEach(() => { MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/projects/`, body: [], }); MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/key-transactions-list/`, body: [], }); MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/legacy-key-transactions-count/`, body: [], }); MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/sessions/`, body: { start: '2021-10-30T00:00:00Z', end: '2021-12-24T00:00:00Z', query: '', intervals: [], groups: [ { by: {project: 1, 'session.status': 'healthy'}, totals: {'sum(session)': 0}, series: {'sum(session)': []}, }, { by: {project: 1, 'session.status': 'crashed'}, totals: {'sum(session)': 0}, series: {'sum(session)': []}, }, { by: {project: 1, 'session.status': 'errored'}, totals: {'sum(session)': 0}, series: {'sum(session)': []}, }, { by: {project: 1, 'session.status': 'abnormal'}, totals: {'sum(session)': 0}, series: {'sum(session)': []}, }, ], }, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/eventsv2/', body: { meta: { user: 'string', transaction: 'string', project: 'string', tpm: 'number', count_unique_user: 'number', count_miserable_user: 'number', user_misery: 'number', }, data: [ { key_transaction: 1, transaction: '/apple/cart', project: project1.slug, tpm: 30, count_unique_user: 1000, count_miserable_user: 122, user_misery: 0.114, project_threshold_config: ['duration', 300], }, ], }, }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team1.slug}/alerts-triggered/`, body: TeamAlertsTriggeredFixture(), }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team1.slug}/alerts-triggered-index/`, body: [], }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team1.slug}/time-to-resolution/`, body: TeamResolutionTimeFixture(), }); MockApiClient.addMockResponse({ method: 'GET', url: `/teams/org-slug/${team1.slug}/release-count/`, body: [], }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team2.slug}/alerts-triggered/`, body: TeamAlertsTriggeredFixture(), }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team2.slug}/alerts-triggered-index/`, body: [], }); MockApiClient.addMockResponse({ url: `/teams/org-slug/${team2.slug}/time-to-resolution/`, body: TeamResolutionTimeFixture(), }); MockApiClient.addMockResponse({ method: 'GET', url: `/teams/org-slug/${team2.slug}/release-count/`, body: [], }); }); beforeEach(() => { TeamStore.reset(); }); afterEach(() => { jest.resetAllMocks(); }); function createWrapper({ projects, teams, isOrgOwner, }: {isOrgOwner?: boolean; projects?: Project[]; teams?: TeamType[]} = {}) { teams = teams ?? [team1, team2, team3]; projects = projects ?? [project1, project2]; ProjectsStore.loadInitialData(projects); const organization = OrganizationFixture({ teams, projects, }); if (isOrgOwner !== undefined && !isOrgOwner) { organization.access = organization.access.filter(scope => scope !== 'org:admin'); } const context = RouterContextFixture([{organization}]); TeamStore.loadInitialData(teams, false, null); MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/events/`, body: [], }); return render(, { context, organization, }); } it('defaults to first team', async () => { createWrapper(); expect(await screen.findByText('#backend')).toBeInTheDocument(); expect(screen.getByText('Key transaction')).toBeInTheDocument(); }); it('allows team switching as non-owner', async () => { createWrapper({isOrgOwner: false}); expect(screen.getByText('#backend')).toBeInTheDocument(); await userEvent.type(screen.getByText('#backend'), '{mouseDown}'); expect(screen.getByText('#frontend')).toBeInTheDocument(); // Teams user is not a member of are hidden expect(screen.queryByText('#internal')).not.toBeInTheDocument(); await userEvent.click(screen.getByText('#frontend')); expect(router.push).toHaveBeenCalledWith( expect.objectContaining({query: {team: team1.id}}) ); expect(localStorage.setItem).toHaveBeenCalledWith( 'teamInsightsSelectedTeamId:org-slug', team1.id ); }); it('allows team switching as owner', async () => { createWrapper(); expect(screen.getByText('#backend')).toBeInTheDocument(); await userEvent.type(screen.getByText('#backend'), '{mouseDown}'); expect(screen.getByText('#frontend')).toBeInTheDocument(); // Org owners can see all teams including ones they are not members of expect(screen.queryByText('#internal')).toBeInTheDocument(); await userEvent.click(screen.getByText('#internal')); expect(router.push).toHaveBeenCalledWith( expect.objectContaining({query: {team: team3.id}}) ); expect(localStorage.setItem).toHaveBeenCalledWith( 'teamInsightsSelectedTeamId:org-slug', team3.id ); }); it('superusers can switch to any team', async () => { jest.mocked(isActiveSuperuser).mockReturnValue(true); createWrapper(); expect(screen.getByText('#backend')).toBeInTheDocument(); await userEvent.type(screen.getByText('#backend'), '{mouseDown}'); expect(screen.getByText('#frontend')).toBeInTheDocument(); // User is not a member of internal team expect(screen.getByText('#internal')).toBeInTheDocument(); }); it('shows users with no teams the join team button', () => { createWrapper({ projects: [{...project1, isMember: false}], teams: [], }); expect(screen.getByText('Join a Team')).toBeInTheDocument(); }); });