Skip to content

Commit c556386

Browse files
authored
feat(issues): Only update group hasSeen when user is member (#92597)
1 parent 8432461 commit c556386

File tree

2 files changed

+67
-74
lines changed

2 files changed

+67
-74
lines changed

static/app/views/issueDetails/groupDetails.spec.tsx

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,12 @@ import OrganizationStore from 'sentry/stores/organizationStore';
1818
import PageFiltersStore from 'sentry/stores/pageFiltersStore';
1919
import ProjectsStore from 'sentry/stores/projectsStore';
2020
import {IssueCategory} from 'sentry/types/group';
21-
import {useNavigate} from 'sentry/utils/useNavigate';
2221
import GroupDetails from 'sentry/views/issueDetails/groupDetails';
2322
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
2423

2524
const SAMPLE_EVENT_ALERT_TEXT =
2625
'You are viewing a sample error. Configure Sentry to start viewing real errors.';
2726

28-
jest.mock('sentry/utils/useNavigate', () => ({
29-
useNavigate: jest.fn(),
30-
}));
31-
3227
jest.mock('sentry/views/issueDetails/utils', () => ({
3328
...jest.requireActual('sentry/views/issueDetails/utils'),
3429
useHasStreamlinedUI: jest.fn(),
@@ -39,31 +34,16 @@ describe('groupDetails', () => {
3934
const group = GroupFixture({issueCategory: IssueCategory.ERROR});
4035
const event = EventFixture();
4136
const project = ProjectFixture({teams: [TeamFixture()]});
37+
let hasSeenMock!: jest.Mock;
4238

43-
const routes = [
44-
{path: '/', childRoutes: []},
45-
{childRoutes: []},
46-
{
47-
path: '/organizations/:orgId/issues/:groupId/',
48-
childRoutes: [],
49-
},
50-
{},
51-
];
52-
53-
const initRouter = {
39+
const initialRouterConfig = {
5440
location: {
5541
pathname: `/organizations/org-slug/issues/${group.id}/`,
56-
query: {project: group.project.id},
57-
},
58-
params: {
59-
groupId: group.id,
6042
},
61-
routes,
43+
route: '/organizations/:orgId/issues/:groupId/',
6244
};
6345

64-
const defaultInit = initializeOrg<{groupId: string}>({
65-
router: initRouter,
66-
});
46+
const defaultInit = initializeOrg<{groupId: string}>({});
6747

6848
const recommendedUser = UserFixture({
6949
options: {
@@ -88,17 +68,19 @@ describe('groupDetails', () => {
8868
return <div>Group Details Mock</div>;
8969
}
9070

91-
const createWrapper = (init = defaultInit) => {
71+
const createWrapper = (
72+
routerConfig = initialRouterConfig,
73+
organization = defaultInit.organization
74+
) => {
9275
// Add project id to the url to skip over the _allp redirect
9376
window.location.search = qs.stringify({project: group.project.id});
9477
return render(
95-
<GroupDetails {...init.routerProps}>
78+
<GroupDetails>
9679
<MockComponent />
9780
</GroupDetails>,
9881
{
99-
organization: init.organization,
100-
router: init.router,
101-
deprecatedRouterMocks: true,
82+
organization,
83+
initialRouterConfig: routerConfig,
10284
}
10385
);
10486
};
@@ -109,7 +91,6 @@ describe('groupDetails', () => {
10991
MockApiClient.clearMockResponses();
11092
OrganizationStore.onUpdate(defaultInit.organization);
11193
act(() => ProjectsStore.loadInitialData(defaultInit.projects));
112-
jest.mocked(useNavigate).mockReturnValue(mockNavigate);
11394

11495
MockApiClient.addMockResponse({
11596
url: `/assistant/`,
@@ -126,7 +107,7 @@ describe('groupDetails', () => {
126107
...event,
127108
},
128109
});
129-
MockApiClient.addMockResponse({
110+
hasSeenMock = MockApiClient.addMockResponse({
130111
url: `/projects/org-slug/${project.slug}/issues/`,
131112
method: 'PUT',
132113
body: {
@@ -190,6 +171,7 @@ describe('groupDetails', () => {
190171

191172
// Sample event alert should not show up
192173
expect(screen.queryByText(SAMPLE_EVENT_ALERT_TEXT)).not.toBeInTheDocument();
174+
expect(hasSeenMock).toHaveBeenCalled();
193175
});
194176

195177
it('renders error when issue is not found', async function () {
@@ -237,16 +219,14 @@ describe('groupDetails', () => {
237219
body: group,
238220
});
239221

240-
const init = initializeOrg({
241-
router: {
242-
...initRouter,
243-
location: LocationFixture({
244-
...initRouter.location,
245-
query: {environment: 'staging', project: group.project.id},
246-
}),
247-
},
248-
});
249-
createWrapper(init);
222+
const routerConfig = {
223+
...initialRouterConfig,
224+
location: LocationFixture({
225+
...initialRouterConfig.location,
226+
query: {environment: 'staging', project: group.project.id},
227+
}),
228+
};
229+
createWrapper(routerConfig);
250230

251231
await waitFor(() =>
252232
expect(mock).toHaveBeenCalledWith(
@@ -272,10 +252,7 @@ describe('groupDetails', () => {
272252
substatus: 'ongoing',
273253
},
274254
});
275-
createWrapper({
276-
...defaultInit,
277-
organization: {...defaultInit.organization},
278-
});
255+
createWrapper();
279256
expect(await screen.findByText('Ongoing')).toBeInTheDocument();
280257
});
281258

@@ -336,30 +313,24 @@ describe('groupDetails', () => {
336313
},
337314
});
338315

339-
createWrapper({
340-
...defaultInit,
341-
router: {
342-
...defaultInit.router,
343-
location: LocationFixture({
344-
query: {
345-
query: 'foo:bar',
346-
statsPeriod: '14d',
347-
},
348-
}),
349-
},
350-
});
316+
const routerConfig = {
317+
...initialRouterConfig,
318+
location: LocationFixture({
319+
...initialRouterConfig.location,
320+
query: {query: 'foo:bar', statsPeriod: '14d'},
321+
}),
322+
};
323+
const {router} = createWrapper(routerConfig);
351324

352325
await waitFor(() => expect(recommendedWithSearchMock).toHaveBeenCalledTimes(1));
353326

354327
await waitFor(() =>
355-
expect(mockNavigate).toHaveBeenCalledWith(
356-
expect.objectContaining(
357-
// query and period should be removed
358-
{query: {}}
359-
),
360-
{
361-
replace: true,
362-
}
328+
expect(router.location).toEqual(
329+
expect.objectContaining({
330+
pathname: routerConfig.location.pathname,
331+
// Query has been removed
332+
query: {},
333+
})
363334
)
364335
);
365336
});
@@ -460,4 +431,31 @@ describe('groupDetails', () => {
460431

461432
await waitFor(() => expect(recommendedMock).toHaveBeenCalledTimes(1));
462433
});
434+
435+
it('does not send hasSeen request when user is not a project member', async function () {
436+
const nonMemberProject = ProjectFixture({
437+
teams: [TeamFixture()],
438+
isMember: false,
439+
});
440+
441+
// Mock the group to belong to the non-member project
442+
MockApiClient.addMockResponse({
443+
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
444+
body: {
445+
...group,
446+
project: nonMemberProject,
447+
hasSeen: false,
448+
},
449+
});
450+
451+
act(() => ProjectsStore.loadInitialData([nonMemberProject]));
452+
453+
createWrapper();
454+
455+
// Wait for the component to render
456+
expect(await screen.findByText(group.shortId)).toBeInTheDocument();
457+
458+
// Verify that the hasSeen request was NOT made
459+
expect(hasSeenMock).not.toHaveBeenCalled();
460+
});
463461
});

static/app/views/issueDetails/groupDetails.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
235235
const navigate = useNavigate();
236236
const defaultIssueEvent = useDefaultIssueEvent();
237237
const hasStreamlinedUI = useHasStreamlinedUI();
238+
const {projects} = useProjects();
238239

239240
const [allProjectChanged, setAllProjectChanged] = useState<boolean>(false);
240241

@@ -399,17 +400,11 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
399400
return;
400401
}
401402

402-
if (!group.hasSeen) {
403+
const project = projects.find(p => p.id === group?.project.id);
404+
if (!group.hasSeen && project?.isMember) {
403405
markEventSeen(api, organization.slug, matchingProjectSlug, params.groupId);
404406
}
405-
}, [
406-
api,
407-
group?.hasSeen,
408-
group?.project?.id,
409-
group?.project?.slug,
410-
organization.slug,
411-
params.groupId,
412-
]);
407+
}, [api, group?.hasSeen, group?.project, organization.slug, params.groupId, projects]);
413408

414409
useEffect(() => {
415410
const locationQuery = qs.parse(window.location.search) || {};

0 commit comments

Comments
 (0)