Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions static/app/components/commandPalette/ui/commandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,13 @@ export function CommandPalette(props: CommandPaletteProps) {
leadingItems: null,
label: (
<Text size="sm" bold variant="primary">
<Flex align="center" gap="md">
<Flex align="center" gap="md" width="100%" minWidth={0}>
<IconDefaultsProvider size="sm">
{action.display.icon}
</IconDefaultsProvider>
{action.display.label}
<Text size="sm" bold variant="primary" ellipsis>
{action.display.label}
</Text>
</Flex>
</Text>
),
Expand Down
166 changes: 165 additions & 1 deletion static/app/views/issueDetails/actions/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {EventStacktraceExceptionFixture} from 'sentry-fixture/eventStacktraceExc
import {GroupFixture} from 'sentry-fixture/group';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';
import {TeamFixture} from 'sentry-fixture/team';
import {UserFixture} from 'sentry-fixture/user';

import {
render,
Expand All @@ -12,19 +14,28 @@ import {
within,
} from 'sentry-test/reactTestingLibrary';

import {
CMDKCollection,
CommandPaletteProvider,
} from 'sentry/components/commandPalette/ui/cmdk';
import {CommandPaletteSlot} from 'sentry/components/commandPalette/ui/commandPaletteSlot';
import {GlobalModal} from 'sentry/components/globalModal';
import {mockTour} from 'sentry/components/tours/testUtils';
import {ConfigStore} from 'sentry/stores/configStore';
import {MemberListStore} from 'sentry/stores/memberListStore';
import {ModalStore} from 'sentry/stores/modalStore';
import {GroupStatus, IssueCategory} from 'sentry/types/group';
import {ProjectsStore} from 'sentry/stores/projectsStore';
import {GroupStatus, IssueCategory, PriorityLevel, type Group} from 'sentry/types/group';
import * as analytics from 'sentry/utils/analytics';
import {getMessage, getTitle} from 'sentry/utils/events';
import {GroupActions} from 'sentry/views/issueDetails/actions';
import {useGroup} from 'sentry/views/issueDetails/useGroup';

const project = ProjectFixture({
id: '2448',
name: 'project name',
slug: 'project',
teams: [TeamFixture({id: '3', slug: 'frontend', name: 'Frontend'})],
});

const group = GroupFixture({
Expand All @@ -51,11 +62,52 @@ jest.mock('sentry/views/issueDetails/issueDetailsTour', () => ({
useIssueDetailsTour: () => mockTour(),
}));

function CommandPaletteTree({
onTree,
}: {
onTree: (tree: ReturnType<ReturnType<typeof CMDKCollection.useStore>['tree']>) => void;
Comment thread
JonasBa marked this conversation as resolved.
Outdated
}) {
const store = CMDKCollection.useStore();
onTree(store.tree());
return null;
}

function SlotOutlets() {
return (
<div style={{display: 'none'}}>
<CommandPaletteSlot.Outlet name="task">
{p => <div {...p} />}
</CommandPaletteSlot.Outlet>
<CommandPaletteSlot.Outlet name="page">
{p => <div {...p} />}
</CommandPaletteSlot.Outlet>
<CommandPaletteSlot.Outlet name="global">
{p => <div {...p} />}
</CommandPaletteSlot.Outlet>
</div>
);
}

describe('GroupActions', () => {
const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');

beforeEach(() => {
ConfigStore.init();
MemberListStore.reset();
ProjectsStore.reset();
ProjectsStore.loadInitialData([project]);
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/users/`,
body: [],
});
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/events//committers/`,
body: {committers: []},
});
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/events//owners/`,
body: {owners: [], rules: []},
});
});
afterEach(() => {
MockApiClient.clearMockResponses();
Expand Down Expand Up @@ -413,4 +465,116 @@ describe('GroupActions', () => {
expect(groupFetchApi).toHaveBeenCalledTimes(2);
});
});

describe('command palette labels', () => {
beforeEach(() => {
ConfigStore.loadInitialData({
user: UserFixture({id: '1', name: 'Test User'}),
} as any);
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/users/`,
method: 'GET',
body: [],
});
MemberListStore.loadInitialData([
UserFixture({id: '1', name: 'Test User'}),
UserFixture({id: '2', name: 'Grace Hopper'}),
UserFixture({id: '7', name: 'Ada Lovelace'}),
]);
});

function renderWithCommandPalette(commandGroup: Group) {
const treeRef: {
current: ReturnType<ReturnType<typeof CMDKCollection.useStore>['tree']>;
} = {current: []};
render(
<CommandPaletteProvider>
<GroupActions
group={commandGroup}
project={project}
disabled={false}
event={null}
/>
<SlotOutlets />
<CommandPaletteTree
onTree={tree => {
treeRef.current = tree;
}}
/>
</CommandPaletteProvider>,
{organization}
);

return treeRef;
}

it('omits stale default state text from task actions', async () => {
const treeRef = renderWithCommandPalette(group);

await waitFor(() => {
expect(treeRef.current.length).toBeGreaterThan(0);
});

const labels = treeRef.current.flatMap(node => [
node.display.label,
...node.children.map(child => child.display.label),
]);
const issueTaskGroup = treeRef.current[0];

expect(labels).toContain('Archive');
expect(labels).toContain('Resolve');
expect(labels).toContain('Set Priority');
expect(labels).toContain('Assign to');
const issueTitle = getTitle(group).title;
const issueMessage = getMessage(group);
expect(issueTaskGroup?.display.label).toBe(
issueMessage && issueMessage !== issueTitle
? `${issueTitle}: ${issueMessage}`
: issueTitle
);
expect(issueTaskGroup?.display.details).toBeUndefined();

expect(labels).not.toContain('Resolve (Unresolved)');
expect(labels).not.toContain('Archive (Active)');
expect(labels).not.toContain('Assign (Unassigned)');
});

it('shows current priority icon and assignee when present', async () => {
const treeRef = renderWithCommandPalette(
GroupFixture({
...group,
priority: PriorityLevel.HIGH,
assignedTo: {
id: '7',
type: 'user',
name: 'Ada Lovelace',
email: 'ada@example.com',
},
})
);

await waitFor(() => {
expect(treeRef.current.length).toBeGreaterThan(0);
});

const issueTaskGroup = treeRef.current[0];
const labels = issueTaskGroup?.children.map(child => child.display.label) ?? [];
const setPriorityAction = issueTaskGroup?.children.find(
child => child.display.label === 'Set Priority'
);
const assignAction = issueTaskGroup?.children.find(
child => child.display.label === 'Assign to'
);
const assignLabels = assignAction?.children.map(child => child.display.label) ?? [];

expect(labels).toContain('Set Priority');
expect(labels).toContain('Assign to');
expect(assignLabels).toContain('Assign to me');
expect(assignLabels).toContain('#frontend');
expect(setPriorityAction?.display.icon).toMatchObject({props: {bars: 3}});
expect(assignAction?.display.icon).toMatchObject({
props: {actor: expect.objectContaining({name: 'Ada Lovelace'})},
});
});
});
});
Loading
Loading