Files
2024-02-15 17:01:50 +00:00

261 lines
8.0 KiB
TypeScript

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(<TeamStatsHealth {...routerProps} />, {
context,
organization,
});
}
it('defaults to first team', () => {
createWrapper();
expect(screen.getByText('#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();
});
});