Skip to content

Commit 2aebd86

Browse files
setchyadufr
authored andcommitted
feat: support discussion state types (gitify-app#827)
* feat: support discussion states * refactor graphql search interface to be generic. * refactor mocks out
1 parent 85e973a commit 2aebd86

File tree

8 files changed

+635
-230
lines changed

8 files changed

+635
-230
lines changed

src/__mocks__/mockedData.ts

Lines changed: 258 additions & 171 deletions
Large diffs are not rendered by default.

src/hooks/useNotifications.test.ts

Lines changed: 206 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import axios from 'axios';
33
import nock from 'nock';
44

55
import { mockAccounts, mockSettings } from '../__mocks__/mock-state';
6-
import { mockedUser } from '../__mocks__/mockedData';
6+
import {
7+
mockedDiscussionNotifications,
8+
mockedUser,
9+
} from '../__mocks__/mockedData';
710
import { AuthState } from '../types';
811
import { useNotifications } from './useNotifications';
912

@@ -191,28 +194,43 @@ describe('hooks/useNotifications.ts', () => {
191194
const notifications = [
192195
{
193196
id: 1,
194-
title: 'This is a notification.',
195-
subject: { type: 'Issue', url: 'https://api.github.com/1' },
197+
subject: {
198+
title: 'This is a notification.',
199+
type: 'Issue',
200+
url: 'https://api.github.com/1',
201+
},
196202
},
197203
{
198204
id: 2,
199-
title: 'A merged PR.',
200-
subject: { type: 'PullRequest', url: 'https://api.github.com/2' },
205+
subject: {
206+
title: 'A merged PR.',
207+
type: 'PullRequest',
208+
url: 'https://api.github.com/2',
209+
},
201210
},
202211
{
203212
id: 3,
204-
title: 'A closed PR.',
205-
subject: { type: 'PullRequest', url: 'https://api.github.com/3' },
213+
subject: {
214+
title: 'A closed PR.',
215+
type: 'PullRequest',
216+
url: 'https://api.github.com/3',
217+
},
206218
},
207219
{
208220
id: 4,
209-
title: 'A draft PR.',
210-
subject: { type: 'PullRequest', url: 'https://api.github.com/4' },
221+
subject: {
222+
title: 'A draft PR.',
223+
type: 'PullRequest',
224+
url: 'https://api.github.com/4',
225+
},
211226
},
212227
{
213228
id: 5,
214-
title: 'A draft PR.',
215-
subject: { type: 'PullRequest', url: 'https://api.github.com/5' },
229+
subject: {
230+
title: 'A draft PR.',
231+
type: 'PullRequest',
232+
url: 'https://api.github.com/5',
233+
},
216234
},
217235
];
218236

@@ -266,6 +284,183 @@ describe('hooks/useNotifications.ts', () => {
266284
result.current.notifications[0].notifications[4].subject.state,
267285
).toBe('draft');
268286
});
287+
288+
it('should fetch discussion notifications with success - with colors', async () => {
289+
const accounts: AuthState = {
290+
...mockAccounts,
291+
enterpriseAccounts: [],
292+
user: mockedUser,
293+
};
294+
295+
nock('https://api.github.com')
296+
.get('/notifications?participating=false')
297+
.reply(200, mockedDiscussionNotifications);
298+
299+
nock('https://api.github.com')
300+
.post('/graphql')
301+
.reply(200, {
302+
data: {
303+
search: {
304+
edges: [
305+
{
306+
node: {
307+
title: 'This is an answered discussion',
308+
viewerSubscription: 'SUBSCRIBED',
309+
stateReason: null,
310+
isAnswered: true,
311+
},
312+
},
313+
],
314+
},
315+
},
316+
})
317+
.post('/graphql')
318+
.reply(200, {
319+
data: {
320+
search: {
321+
edges: [
322+
{
323+
node: {
324+
title: 'This is a duplicate discussion',
325+
viewerSubscription: 'SUBSCRIBED',
326+
stateReason: 'DUPLICATE',
327+
isAnswered: false,
328+
},
329+
},
330+
],
331+
},
332+
},
333+
})
334+
.post('/graphql')
335+
.reply(200, {
336+
data: {
337+
search: {
338+
edges: [
339+
{
340+
node: {
341+
title: 'This is an open discussion',
342+
viewerSubscription: 'SUBSCRIBED',
343+
stateReason: null,
344+
isAnswered: false,
345+
},
346+
},
347+
{
348+
node: {
349+
title: 'This is an open discussion',
350+
viewerSubscription: 'IGNORED',
351+
stateReason: null,
352+
isAnswered: false,
353+
},
354+
},
355+
],
356+
},
357+
},
358+
})
359+
.post('/graphql')
360+
.reply(200, {
361+
data: {
362+
search: {
363+
edges: [
364+
{
365+
node: {
366+
title: 'This is nm outdated discussion',
367+
viewerSubscription: 'SUBSCRIBED',
368+
stateReason: 'OUTDATED',
369+
isAnswered: false,
370+
},
371+
},
372+
],
373+
},
374+
},
375+
})
376+
.post('/graphql')
377+
.reply(200, {
378+
data: {
379+
search: {
380+
edges: [
381+
{
382+
node: {
383+
title: 'This is a reopened discussion',
384+
viewerSubscription: 'SUBSCRIBED',
385+
stateReason: 'REOPENED',
386+
isAnswered: false,
387+
},
388+
},
389+
],
390+
},
391+
},
392+
})
393+
.post('/graphql')
394+
.reply(200, {
395+
data: {
396+
search: {
397+
edges: [
398+
{
399+
node: {
400+
title: 'This is a resolved discussion',
401+
viewerSubscription: 'SUBSCRIBED',
402+
stateReason: 'RESOLVED',
403+
isAnswered: false,
404+
},
405+
},
406+
],
407+
},
408+
},
409+
})
410+
.post('/graphql')
411+
.reply(200, {
412+
data: {
413+
search: {
414+
edges: [
415+
{
416+
node: {
417+
title: 'unknown search result',
418+
viewerSubscription: 'SUBSCRIBED',
419+
stateReason: null,
420+
isAnswered: false,
421+
},
422+
},
423+
],
424+
},
425+
},
426+
});
427+
428+
const { result } = renderHook(() => useNotifications(true));
429+
430+
act(() => {
431+
result.current.fetchNotifications(accounts, {
432+
...mockSettings,
433+
colors: true,
434+
});
435+
});
436+
437+
expect(result.current.isFetching).toBe(true);
438+
439+
await waitFor(() => {
440+
expect(result.current.notifications[0].hostname).toBe('github.com');
441+
});
442+
443+
const resultNotifications = result.current.notifications[0];
444+
445+
expect(resultNotifications.notifications.length).toBe(7);
446+
expect(resultNotifications.notifications[0].subject.state).toBe(
447+
'ANSWERED',
448+
);
449+
expect(resultNotifications.notifications[1].subject.state).toBe(
450+
'DUPLICATE',
451+
);
452+
expect(resultNotifications.notifications[2].subject.state).toBe('OPEN');
453+
expect(resultNotifications.notifications[3].subject.state).toBe(
454+
'OUTDATED',
455+
);
456+
expect(resultNotifications.notifications[4].subject.state).toBe(
457+
'REOPENED',
458+
);
459+
expect(resultNotifications.notifications[5].subject.state).toBe(
460+
'RESOLVED',
461+
);
462+
expect(resultNotifications.notifications[6].subject.state).toBe('OPEN');
463+
});
269464
});
270465
});
271466

src/hooks/useNotifications.ts

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getEnterpriseAccountToken,
1010
generateGitHubAPIUrl,
1111
isEnterpriseHost,
12+
getDiscussionState,
1213
} from '../utils/helpers';
1314
import { removeNotification } from '../utils/remove-notification';
1415
import { triggerNativeNotifications } from '../utils/notifications';
@@ -137,12 +138,6 @@ export const useNotifications = (colors: boolean): NotificationsState => {
137138
notifications: await axios.all<Notification>(
138139
accountNotifications.notifications.map(
139140
async (notification: Notification) => {
140-
if (
141-
notification.subject.type !== 'PullRequest' &&
142-
notification.subject.type !== 'Issue'
143-
) {
144-
return notification;
145-
}
146141
const isEnterprise = isEnterpriseHost(
147142
accountNotifications.hostname,
148143
);
@@ -153,28 +148,47 @@ export const useNotifications = (colors: boolean): NotificationsState => {
153148
)
154149
: accounts.token;
155150

156-
const cardinalData = (
157-
await apiRequestAuth(
158-
notification.subject.url,
159-
'GET',
160-
token,
161-
)
162-
).data;
163-
164-
const state =
165-
cardinalData.state === 'closed'
166-
? cardinalData.state_reason ||
167-
(cardinalData.merged && 'merged') ||
168-
'closed'
169-
: (cardinalData.draft && 'draft') || 'open';
170-
171-
return {
172-
...notification,
173-
subject: {
174-
...notification.subject,
175-
state,
176-
},
177-
};
151+
switch (notification.subject.type) {
152+
case 'Discussion':
153+
const discussionState = await getDiscussionState(
154+
notification,
155+
token,
156+
);
157+
158+
return {
159+
...notification,
160+
subject: {
161+
...notification.subject,
162+
state: discussionState,
163+
},
164+
};
165+
case 'Issue':
166+
case 'PullRequest':
167+
const cardinalData = (
168+
await apiRequestAuth(
169+
notification.subject.url,
170+
'GET',
171+
token,
172+
)
173+
).data;
174+
175+
const state =
176+
cardinalData.state === 'closed'
177+
? cardinalData.state_reason ||
178+
(cardinalData.merged && 'merged') ||
179+
'closed'
180+
: (cardinalData.draft && 'draft') || 'open';
181+
182+
return {
183+
...notification,
184+
subject: {
185+
...notification.subject,
186+
state,
187+
},
188+
};
189+
default:
190+
return notification;
191+
}
178192
},
179193
),
180194
),

0 commit comments

Comments
 (0)