Skip to content

Commit 692becb

Browse files
authored
feat(issue-priority): Add priority column to issue stream (#64820)
- Add priority column - Adjust assignee column to have take up less space (tested this even with 3 suggested assignees, still looks good) - Adjust responsive elements to hide columns and actions sooner because of the extra required space. (I decided to make these changes without the feature flag because it works fine without it and it was already a bit crowded at smaller screen sizes)
1 parent 782b0f4 commit 692becb

File tree

10 files changed

+123
-23
lines changed

10 files changed

+123
-23
lines changed

fixtures/page_objects/issue_list.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,21 @@ def delete_issues(self):
4747
self.browser.click('[data-test-id="confirm-button"]')
4848

4949
def merge_issues(self):
50-
self.browser.click('[aria-label="Merge Selected Issues"]')
51-
self.browser.click('[data-test-id="confirm-button"]')
50+
# Merge button gets put into an overflow menu for small viewports
51+
if self.browser.element_exists('[aria-label="Merge Selected Issues"]'):
52+
self.browser.click('[aria-label="Merge Selected Issues"]')
53+
self.browser.click('[data-test-id="confirm-button"]')
54+
else:
55+
self.browser.click('[aria-label="More issue actions"]')
56+
self.browser.wait_until('[data-test-id="merge"]')
57+
self.browser.click('[data-test-id="merge"]')
58+
self.browser.click('[data-test-id="confirm-button"]')
5259

5360
def mark_reviewed_issues(self):
54-
self.browser.click('[aria-label="Mark Reviewed"]')
61+
# Marked reviewed button gets put into an overflow menu for small viewports
62+
if self.browser.element_exists('[aria-label="Mark Reviewed"]'):
63+
self.browser.click('[aria-label="Mark Reviewed"]')
64+
else:
65+
self.browser.click('[aria-label="More issue actions"]')
66+
self.browser.wait_until('[data-test-id="mark-reviewed"]')
67+
self.browser.click('[data-test-id="mark-reviewed"]')

static/app/components/issues/groupList.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ const defaultProps = {
4141
withColumns: ['graph', 'event', 'users', 'assignee'] satisfies GroupListColumn[],
4242
};
4343

44-
export type GroupListColumn = 'graph' | 'event' | 'users' | 'assignee' | 'lastTriggered';
44+
export type GroupListColumn =
45+
| 'graph'
46+
| 'event'
47+
| 'users'
48+
| 'priority'
49+
| 'assignee'
50+
| 'lastTriggered';
4551

4652
type Props = WithRouterProps & {
4753
api: Client;

static/app/components/issues/groupListHeader.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
33
import PanelHeader from 'sentry/components/panels/panelHeader';
44
import {t} from 'sentry/locale';
55
import {space} from 'sentry/styles/space';
6+
import useOrganization from 'sentry/utils/useOrganization';
67

78
import type {GroupListColumn} from './groupList';
89

@@ -17,6 +18,8 @@ function GroupListHeader({
1718
narrowGroups = false,
1819
withColumns = ['graph', 'event', 'users', 'assignee', 'lastTriggered'],
1920
}: Props) {
21+
const organization = useOrganization();
22+
2023
return (
2124
<PanelHeader disablePadding>
2225
<IssueWrapper>{t('Issue')}</IssueWrapper>
@@ -27,6 +30,10 @@ function GroupListHeader({
2730
<EventUserWrapper>{t('events')}</EventUserWrapper>
2831
)}
2932
{withColumns.includes('users') && <EventUserWrapper>{t('users')}</EventUserWrapper>}
33+
{withColumns.includes('priority') &&
34+
organization.features.includes('issue-priority-ui') && (
35+
<PriorityWrapper narrowGroups={narrowGroups}>{t('Priority')}</PriorityWrapper>
36+
)}
3037
{withColumns.includes('assignee') && (
3138
<AssigneeWrapper narrowGroups={narrowGroups}>{t('Assignee')}</AssigneeWrapper>
3239
)}
@@ -70,14 +77,24 @@ const ChartWrapper = styled(Heading)<{narrowGroups: boolean}>`
7077
width: 160px;
7178
7279
@media (max-width: ${p =>
73-
p.narrowGroups ? p.theme.breakpoints.xlarge : p.theme.breakpoints.large}) {
80+
p.narrowGroups ? p.theme.breakpoints.xxlarge : p.theme.breakpoints.xlarge}) {
81+
display: none;
82+
}
83+
`;
84+
85+
const PriorityWrapper = styled(Heading)<{narrowGroups: boolean}>`
86+
justify-content: flex-end;
87+
width: 85px;
88+
89+
@media (max-width: ${p =>
90+
p.narrowGroups ? p.theme.breakpoints.large : p.theme.breakpoints.medium}) {
7491
display: none;
7592
}
7693
`;
7794

7895
const AssigneeWrapper = styled(Heading)<{narrowGroups: boolean}>`
7996
justify-content: flex-end;
80-
width: 80px;
97+
width: 60px;
8198
8299
@media (max-width: ${p =>
83100
p.narrowGroups ? p.theme.breakpoints.large : p.theme.breakpoints.medium}) {

static/app/components/stream/group.spec.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {GroupFixture} from 'sentry-fixture/group';
2+
import {OrganizationFixture} from 'sentry-fixture/organization';
23
import {ProjectFixture} from 'sentry-fixture/project';
34

45
import {initializeOrg} from 'sentry-test/initializeOrg';
5-
import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
6+
import {act, render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
67

78
import StreamGroup from 'sentry/components/stream/group';
89
import GroupStore from 'sentry/stores/groupStore';
910
import GuideStore from 'sentry/stores/guideStore';
1011
import type {GroupStatusResolution, MarkReviewed} from 'sentry/types';
11-
import {EventOrGroupType, GroupStatus} from 'sentry/types';
12+
import {EventOrGroupType, GroupStatus, PriorityLevel} from 'sentry/types';
1213
import {trackAnalytics} from 'sentry/utils/analytics';
1314

1415
jest.mock('sentry/utils/analytics');
@@ -89,6 +90,32 @@ describe('StreamGroup', function () {
8990
expect(screen.getByTestId('resolved-issue')).toBeInTheDocument();
9091
});
9192

93+
it('can change priority', async function () {
94+
const mockModifyGroup = MockApiClient.addMockResponse({
95+
url: '/projects/org-slug/foo-project/issues/',
96+
method: 'PUT',
97+
body: {priority: PriorityLevel.HIGH},
98+
});
99+
100+
render(<StreamGroup id="1337" query="is:unresolved" />, {
101+
organization: OrganizationFixture({features: ['issue-priority-ui']}),
102+
});
103+
104+
const priorityDropdown = screen.getByRole('button', {name: 'Modify issue priority'});
105+
expect(within(priorityDropdown).getByText('Medium')).toBeInTheDocument();
106+
await userEvent.click(priorityDropdown);
107+
await userEvent.click(screen.getByRole('menuitemradio', {name: 'High'}));
108+
expect(within(priorityDropdown).getByText('High')).toBeInTheDocument();
109+
expect(mockModifyGroup).toHaveBeenCalledWith(
110+
'/projects/org-slug/foo-project/issues/',
111+
expect.objectContaining({
112+
data: expect.objectContaining({
113+
priority: 'high',
114+
}),
115+
})
116+
);
117+
});
118+
92119
it('tracks clicks from issues stream', async function () {
93120
const {routerContext, organization} = initializeOrg();
94121
render(

static/app/components/stream/group.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
4444
import usePageFilters from 'sentry/utils/usePageFilters';
4545
import withOrganization from 'sentry/utils/withOrganization';
4646
import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
47+
import GroupPriority from 'sentry/views/issueDetails/groupPriority';
4748
import {
4849
DISCOVER_EXCLUSION_FIELDS,
4950
getTabs,
@@ -87,7 +88,7 @@ function BaseGroupRow({
8788
statsPeriod = DEFAULT_STREAM_GROUP_STATS_PERIOD,
8889
canSelect = true,
8990
withChart = true,
90-
withColumns = ['graph', 'event', 'users', 'assignee', 'lastTriggered'],
91+
withColumns = ['graph', 'event', 'users', 'priority', 'assignee', 'lastTriggered'],
9192
useFilteredStats = false,
9293
useTintRow = true,
9394
narrowGroups = false,
@@ -454,6 +455,12 @@ function BaseGroupRow({
454455
{withColumns.includes('users') && issueTypeConfig.stats.enabled && (
455456
<EventCountsWrapper>{groupUsersCount}</EventCountsWrapper>
456457
)}
458+
{organization.features.includes('issue-priority-ui') &&
459+
withColumns.includes('priority') ? (
460+
<PriorityWrapper narrowGroups={narrowGroups}>
461+
{group.priority ? <GroupPriority group={group} /> : null}
462+
</PriorityWrapper>
463+
) : null}
457464
{withColumns.includes('assignee') && (
458465
<AssigneeWrapper narrowGroups={narrowGroups}>
459466
<AssigneeSelector
@@ -585,8 +592,7 @@ const ChartWrapper = styled('div')<{narrowGroups: boolean}>`
585592
width: 200px;
586593
align-self: center;
587594
588-
@media (max-width: ${p =>
589-
p.narrowGroups ? p.theme.breakpoints.xlarge : p.theme.breakpoints.large}) {
595+
@media (max-width: ${p => (p.narrowGroups ? '1600px' : p.theme.breakpoints.xlarge)}) {
590596
display: none;
591597
}
592598
`;
@@ -603,8 +609,21 @@ const EventCountsWrapper = styled('div')`
603609
}
604610
`;
605611

612+
const PriorityWrapper = styled('div')<{narrowGroups: boolean}>`
613+
width: 85px;
614+
margin: 0 ${space(2)};
615+
align-self: center;
616+
display: flex;
617+
justify-content: flex-end;
618+
619+
@media (max-width: ${p =>
620+
p.narrowGroups ? p.theme.breakpoints.large : p.theme.breakpoints.medium}) {
621+
display: none;
622+
}
623+
`;
624+
606625
const AssigneeWrapper = styled('div')<{narrowGroups: boolean}>`
607-
width: 80px;
626+
width: 60px;
608627
margin: 0 ${space(2)};
609628
align-self: center;
610629

static/app/views/alerts/rules/issue/previewTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function PreviewTable({
7070
withChart={false}
7171
canSelect={false}
7272
showLastTriggered
73+
withColumns={['assignee', 'event', 'lastTriggered', 'users']}
7374
/>
7475
);
7576
});

static/app/views/issueDetails/groupPriority.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function GroupPriority({group}: GroupDetailsPriorityProps) {
3030
});
3131

3232
addLoadingMessage(t('Saving changes\u2026'));
33+
IssueListCacheStore.reset();
3334

3435
bulkUpdate(
3536
api,
@@ -41,8 +42,6 @@ function GroupPriority({group}: GroupDetailsPriorityProps) {
4142
},
4243
{complete: clearIndicators}
4344
);
44-
45-
IssueListCacheStore.reset();
4645
};
4746

4847
return (

static/app/views/issueList/actions/actionSet.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {Fragment} from 'react';
2-
import {useTheme} from '@emotion/react';
32

43
import ActionLink from 'sentry/components/actions/actionLink';
54
import ArchiveActions from 'sentry/components/actions/archive';
@@ -103,8 +102,7 @@ function ActionSet({
103102

104103
// Determine whether to nest "Merge" and "Mark as Reviewed" buttons inside
105104
// the dropdown menu based on the current screen size
106-
const theme = useTheme();
107-
const nestMergeAndReview = useMedia(`(max-width: ${theme.breakpoints.xlarge})`);
105+
const nestMergeAndReview = useMedia(`(max-width: 1700px`);
108106

109107
const menuItems: MenuItemProps[] = [
110108
{

static/app/views/issueList/actions/headers.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ToolbarHeader from 'sentry/components/toolbarHeader';
55
import {t} from 'sentry/locale';
66
import {space} from 'sentry/styles/space';
77
import type {PageFilters} from 'sentry/types';
8+
import useOrganization from 'sentry/utils/useOrganization';
89

910
type Props = {
1011
isReprocessingQuery: boolean;
@@ -21,6 +22,8 @@ function Headers({
2122
isReprocessingQuery,
2223
isSavedSearchesOpen,
2324
}: Props) {
25+
const organization = useOrganization();
26+
2427
return (
2528
<Fragment>
2629
{isReprocessingQuery ? (
@@ -52,9 +55,14 @@ function Headers({
5255
</GraphHeaderWrapper>
5356
<EventsOrUsersLabel>{t('Events')}</EventsOrUsersLabel>
5457
<EventsOrUsersLabel>{t('Users')}</EventsOrUsersLabel>
55-
<AssigneesLabel isSavedSearchesOpen={isSavedSearchesOpen}>
58+
{organization.features.includes('issue-priority-ui') && (
59+
<PriorityLabel isSavedSearchesOpen={isSavedSearchesOpen}>
60+
<ToolbarHeader>{t('Priority')}</ToolbarHeader>
61+
</PriorityLabel>
62+
)}
63+
<AssigneeLabel isSavedSearchesOpen={isSavedSearchesOpen}>
5664
<ToolbarHeader>{t('Assignee')}</ToolbarHeader>
57-
</AssigneesLabel>
65+
</AssigneeLabel>
5866
</Fragment>
5967
)}
6068
</Fragment>
@@ -70,7 +78,7 @@ const GraphHeaderWrapper = styled('div')<{isSavedSearchesOpen?: boolean}>`
7078
animation: 0.25s FadeIn linear forwards;
7179
7280
@media (max-width: ${p =>
73-
p.isSavedSearchesOpen ? p.theme.breakpoints.xlarge : p.theme.breakpoints.large}) {
81+
p.isSavedSearchesOpen ? '1600px' : p.theme.breakpoints.xlarge}) {
7482
display: none;
7583
}
7684
@@ -117,10 +125,22 @@ const EventsOrUsersLabel = styled(ToolbarHeader)`
117125
}
118126
`;
119127

120-
const AssigneesLabel = styled('div')<{isSavedSearchesOpen?: boolean}>`
128+
const PriorityLabel = styled('div')<{isSavedSearchesOpen?: boolean}>`
121129
justify-content: flex-end;
122130
text-align: right;
123-
width: 80px;
131+
width: 85px;
132+
margin: 0 ${space(2)};
133+
134+
@media (max-width: ${p =>
135+
p.isSavedSearchesOpen ? p.theme.breakpoints.large : p.theme.breakpoints.medium}) {
136+
display: none;
137+
}
138+
`;
139+
140+
const AssigneeLabel = styled('div')<{isSavedSearchesOpen?: boolean}>`
141+
justify-content: flex-end;
142+
text-align: right;
143+
width: 60px;
124144
margin-left: ${space(2)};
125145
margin-right: ${space(2)};
126146

static/app/views/issueList/actions/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function IssueListActions({
7575

7676
const disableActions = useMedia(
7777
`(max-width: ${
78-
isSavedSearchesOpen ? theme.breakpoints.large : theme.breakpoints.small
78+
isSavedSearchesOpen ? theme.breakpoints.xlarge : theme.breakpoints.medium
7979
})`
8080
);
8181

0 commit comments

Comments
 (0)