Skip to content

Commit b1cb56b

Browse files
authored
refactor: extract discussions api calls into client (#1085)
* refactor: extract discussions api calls into api/client * fix query * update query variable name * update comments
1 parent cbc4e84 commit b1cb56b

File tree

11 files changed

+208
-159
lines changed

11 files changed

+208
-159
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"css-loader": "7.1.1",
139139
"electron": "30.0.1",
140140
"electron-builder": "24.13.3",
141+
"graphql-tag": "2.12.6",
141142
"husky": "9.0.11",
142143
"jest": "29.7.0",
143144
"jest-environment-jsdom": "29.7.0",

pnpm-lock.yaml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/__mocks__/mockedData.ts

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -473,41 +473,37 @@ export const mockDiscussionComments: DiscussionComments = {
473473

474474
export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
475475
data: {
476-
data: {
477-
search: {
478-
nodes: [
479-
{
480-
viewerSubscription: 'SUBSCRIBED',
481-
title: '1.16.0',
482-
isAnswered: false,
483-
stateReason: 'OPEN',
484-
url: 'https://github.com/gitify-app/notifications-test/discussions/612',
485-
author: {
486-
login: 'discussion-creator',
487-
url: 'https://github.com/discussion-creator',
488-
avatar_url:
489-
'https://avatars.githubusercontent.com/u/123456789?v=4',
490-
type: 'User',
491-
},
492-
comments: mockDiscussionComments,
476+
search: {
477+
nodes: [
478+
{
479+
viewerSubscription: 'SUBSCRIBED',
480+
title: '1.16.0',
481+
isAnswered: false,
482+
stateReason: 'OPEN',
483+
url: 'https://github.com/gitify-app/notifications-test/discussions/612',
484+
author: {
485+
login: 'discussion-creator',
486+
url: 'https://github.com/discussion-creator',
487+
avatar_url: 'https://avatars.githubusercontent.com/u/123456789?v=4',
488+
type: 'User',
493489
},
494-
{
495-
viewerSubscription: 'IGNORED',
496-
title: '1.16.0',
497-
isAnswered: false,
498-
stateReason: 'ANSWERED',
499-
url: 'https://github.com/gitify-app/notifications-test/discussions/123',
500-
author: {
501-
login: 'discussion-creator',
502-
url: 'https://github.com/discussion-creator',
503-
avatar_url:
504-
'https://avatars.githubusercontent.com/u/123456789?v=4',
505-
type: 'User',
506-
},
507-
comments: mockDiscussionComments,
490+
comments: mockDiscussionComments,
491+
},
492+
{
493+
viewerSubscription: 'IGNORED',
494+
title: '1.16.0',
495+
isAnswered: false,
496+
stateReason: 'ANSWERED',
497+
url: 'https://github.com/gitify-app/notifications-test/discussions/123',
498+
author: {
499+
login: 'discussion-creator',
500+
url: 'https://github.com/discussion-creator',
501+
avatar_url: 'https://avatars.githubusercontent.com/u/123456789?v=4',
502+
type: 'User',
508503
},
509-
],
510-
},
504+
comments: mockDiscussionComments,
505+
},
506+
],
511507
},
512508
},
513509
};

src/typesGitHub.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,8 @@ export interface Release {
407407

408408
export interface GraphQLSearch<T> {
409409
data: {
410-
data: {
411-
search: {
412-
nodes: T[];
413-
};
410+
search: {
411+
nodes: T[];
414412
};
415413
};
416414
}

src/utils/api/client.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { SettingsState } from '../../types';
33
import type {
44
Commit,
55
CommitComment,
6+
Discussion,
7+
GraphQLSearch,
68
Issue,
79
IssueOrPullRequestComment,
810
Notification,
@@ -15,6 +17,11 @@ import type {
1517
import { getGitHubAPIBaseUrl } from '../helpers';
1618
import { apiRequestAuth } from './request';
1719

20+
import { print } from 'graphql/language/printer';
21+
import Constants from '../constants';
22+
import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions';
23+
import { formatSearchQueryString } from './utils';
24+
1825
/**
1926
* Get Hypermedia links to resources accessible in GitHub's REST API
2027
*
@@ -213,3 +220,28 @@ export async function getHtmlUrl(url: string, token: string): Promise<string> {
213220
console.error('Failed to get html url');
214221
}
215222
}
223+
224+
/**
225+
* Search for Discussions that match notification title and repository.
226+
*
227+
* Returns first 10 matching discussions and their latest comments / replies
228+
*
229+
*/
230+
export async function searchDiscussions(
231+
notification: Notification,
232+
token: string,
233+
): AxiosPromise<GraphQLSearch<Discussion>> {
234+
return apiRequestAuth(Constants.GITHUB_API_GRAPHQL_URL, 'POST', token, {
235+
query: print(QUERY_SEARCH_DISCUSSIONS),
236+
variables: {
237+
queryStatement: formatSearchQueryString(
238+
notification.repository.full_name,
239+
notification.subject.title,
240+
notification.updated_at,
241+
),
242+
firstDiscussions: 10,
243+
lastComments: 100,
244+
lastReplies: 1,
245+
},
246+
});
247+
}

src/utils/api/graphql/discussions.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import gql from 'graphql-tag';
2+
3+
const FRAGMENT_AUTHOR = gql`
4+
fragment AuthorFields on Actor {
5+
login
6+
url
7+
avatar_url: avatarUrl
8+
type: __typename
9+
}
10+
`;
11+
12+
const FRAGMENT_COMMENTS = gql`
13+
fragment CommentFields on DiscussionComment {
14+
databaseId
15+
createdAt
16+
author {
17+
...AuthorFields
18+
}
19+
}
20+
21+
${FRAGMENT_AUTHOR}
22+
`;
23+
24+
export const QUERY_SEARCH_DISCUSSIONS = gql`
25+
query fetchDiscussions(
26+
$queryStatement: String!,
27+
$firstDiscussions: Int,
28+
$lastComments: Int,
29+
$lastReplies: Int
30+
) {
31+
search(query:$queryStatement, type: DISCUSSION, first: $firstDiscussions) {
32+
nodes {
33+
... on Discussion {
34+
viewerSubscription
35+
title
36+
stateReason
37+
isAnswered
38+
url
39+
author {
40+
...AuthorFields
41+
}
42+
comments(last: $lastComments){
43+
nodes {
44+
...CommentFields
45+
replies(last: $lastReplies) {
46+
nodes {
47+
...CommentFields
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
56+
57+
${FRAGMENT_AUTHOR}
58+
${FRAGMENT_COMMENTS}
59+
`;

src/utils/api/utils.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { addHours, formatSearchQueryString } from './utils';
2+
3+
describe('utils/api/utils.ts', () => {
4+
describe('addHours', () => {
5+
test('adds hours correctly for positive values', () => {
6+
const result = addHours('2024-02-20T12:00:00.000Z', 3);
7+
expect(result).toBe('2024-02-20T15:00:00.000Z');
8+
});
9+
10+
test('adds hours correctly for negative values', () => {
11+
const result = addHours('2024-02-20T12:00:00.000Z', -2);
12+
expect(result).toBe('2024-02-20T10:00:00.000Z');
13+
});
14+
});
15+
16+
describe('formatSearchQueryString', () => {
17+
test('formats search query string correctly', () => {
18+
const result = formatSearchQueryString(
19+
'exampleRepo',
20+
'exampleTitle',
21+
'2024-02-20T12:00:00.000Z',
22+
);
23+
24+
expect(result).toBe(
25+
'exampleTitle in:title repo:exampleRepo updated:>2024-02-20T10:00:00.000Z',
26+
);
27+
});
28+
});
29+
});

src/utils/api/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function formatSearchQueryString(
2+
repo: string,
3+
title: string,
4+
lastUpdated: string,
5+
): string {
6+
return `${title} in:title repo:${repo} updated:>${addHours(lastUpdated, -2)}`;
7+
}
8+
9+
export function addHours(date: string, hours: number): string {
10+
return new Date(new Date(date).getTime() + hours * 36e5).toISOString();
11+
}

src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const Constants = {
1111
},
1212

1313
GITHUB_API_BASE_URL: 'https://api.github.com',
14+
GITHUB_API_GRAPHQL_URL: 'https://api.github.com/graphql',
1415

1516
REPO_SLUG: 'gitify-app/gitify',
1617

src/utils/helpers.test.ts

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ import {
88
import type { SubjectType } from '../typesGitHub';
99
import * as apiRequests from './api/request';
1010
import {
11-
addHours,
1211
addNotificationReferrerIdToUrl,
1312
formatForDisplay,
14-
formatSearchQueryString,
1513
generateGitHubWebUrl,
1614
generateNotificationReferrerId,
1715
getGitHubAPIBaseUrl,
@@ -102,33 +100,6 @@ describe('utils/helpers.ts', () => {
102100
});
103101
});
104102

105-
describe('addHours', () => {
106-
// Example test using Jest
107-
test('adds hours correctly for positive values', () => {
108-
const result = addHours('2024-02-20T12:00:00.000Z', 3);
109-
expect(result).toBe('2024-02-20T15:00:00.000Z');
110-
});
111-
112-
test('adds hours correctly for negative values', () => {
113-
const result = addHours('2024-02-20T12:00:00.000Z', -2);
114-
expect(result).toBe('2024-02-20T10:00:00.000Z');
115-
});
116-
});
117-
118-
describe('formatSearchQueryString', () => {
119-
test('formats search query string correctly', () => {
120-
const result = formatSearchQueryString(
121-
'exampleRepo',
122-
'exampleTitle',
123-
'2024-02-20T12:00:00.000Z',
124-
);
125-
126-
expect(result).toBe(
127-
'exampleTitle in:title repo:exampleRepo updated:>2024-02-20T10:00:00.000Z',
128-
);
129-
});
130-
});
131-
132103
describe('generateGitHubWebUrl', () => {
133104
const mockedHtmlUrl =
134105
'https://github.com/gitify-app/notifications-test/issues/785';
@@ -398,7 +369,7 @@ describe('utils/helpers.ts', () => {
398369
);
399370
});
400371

401-
it('when no subject urls and no discussions found via query, default to linking to repository discussions', async () => {
372+
it('link to matching discussion and comment hash', async () => {
402373
const subject = {
403374
title: '1.16.0',
404375
url: null,
@@ -407,7 +378,11 @@ describe('utils/helpers.ts', () => {
407378
};
408379

409380
const requestPromise = new Promise((resolve) =>
410-
resolve(mockedGraphQLResponse as AxiosResponse),
381+
resolve({
382+
data: {
383+
...mockedGraphQLResponse,
384+
},
385+
} as AxiosResponse),
411386
) as AxiosPromise;
412387

413388
apiRequestAuthMock.mockResolvedValue(requestPromise);

0 commit comments

Comments
 (0)