diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index 228ae1558..24f87f73c 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -1,5 +1,11 @@ import { AccountNotifications, EnterpriseAccount } from '../types'; -import { Notification, Repository, User, GraphQLSearch } from '../typesGithub'; +import { + Notification, + Repository, + User, + GraphQLSearch, + DiscussionSearchResultEdge, +} from '../typesGithub'; export const mockedEnterpriseAccounts: EnterpriseAccount[] = [ { @@ -276,217 +282,298 @@ export const mockedSingleAccountNotifications: AccountNotifications[] = [ }, ]; -export const mockedGraphQLResponse: GraphQLSearch = { - data: { +export const mockedGraphQLResponse: GraphQLSearch = + { data: { - search: { - edges: [ - { - node: { - viewerSubscription: 'SUBSCRIBED', - title: '1.16.0', - url: 'https://github.com/manosim/notifications-test/discussions/612', - comments: { - edges: [ - { - node: { - databaseId: 2215656, - createdAt: '2022-02-20T18:33:39Z', - replies: { - edges: [], + data: { + search: { + edges: [ + { + node: { + viewerSubscription: 'SUBSCRIBED', + title: '1.16.0', + url: 'https://github.com/manosim/notifications-test/discussions/612', + comments: { + edges: [ + { + node: { + databaseId: 2215656, + createdAt: '2022-02-20T18:33:39Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2217789, - createdAt: '2022-02-21T03:30:42Z', - replies: { - edges: [], + { + node: { + databaseId: 2217789, + createdAt: '2022-02-21T03:30:42Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2223243, - createdAt: '2022-02-21T18:26:27Z', - replies: { - edges: [ - { - node: { - databaseId: 2232922, - createdAt: '2022-02-23T00:57:58Z', + { + node: { + databaseId: 2223243, + createdAt: '2022-02-21T18:26:27Z', + replies: { + edges: [ + { + node: { + databaseId: 2232922, + createdAt: '2022-02-23T00:57:58Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2232921, - createdAt: '2022-02-23T00:57:49Z', - replies: { - edges: [], + { + node: { + databaseId: 2232921, + createdAt: '2022-02-23T00:57:49Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2258799, - createdAt: '2022-02-27T01:22:20Z', - replies: { - edges: [ - { - node: { - databaseId: 2300902, - createdAt: '2022-03-05T17:43:52Z', + { + node: { + databaseId: 2258799, + createdAt: '2022-02-27T01:22:20Z', + replies: { + edges: [ + { + node: { + databaseId: 2300902, + createdAt: '2022-03-05T17:43:52Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2297637, - createdAt: '2022-03-04T20:39:44Z', - replies: { - edges: [ - { - node: { - databaseId: 2300893, - createdAt: '2022-03-05T17:41:04Z', + { + node: { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + replies: { + edges: [ + { + node: { + databaseId: 2300893, + createdAt: '2022-03-05T17:41:04Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2299763, - createdAt: '2022-03-05T11:05:42Z', - replies: { - edges: [ - { - node: { - databaseId: 2300895, - createdAt: '2022-03-05T17:41:44Z', + { + node: { + databaseId: 2299763, + createdAt: '2022-03-05T11:05:42Z', + replies: { + edges: [ + { + node: { + databaseId: 2300895, + createdAt: '2022-03-05T17:41:44Z', + }, }, - }, - ], + ], + }, }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - viewerSubscription: 'IGNORED', - title: '1.16.0', - url: 'https://github.com/manosim/notifications-test/discussions/612', - comments: { - edges: [ - { - node: { - databaseId: 2215656, - createdAt: '2022-02-20T18:33:39Z', - replies: { - edges: [], + { + node: { + viewerSubscription: 'IGNORED', + title: '1.16.0', + url: 'https://github.com/manosim/notifications-test/discussions/612', + comments: { + edges: [ + { + node: { + databaseId: 2215656, + createdAt: '2022-02-20T18:33:39Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2217789, - createdAt: '2022-02-21T03:30:42Z', - replies: { - edges: [], + { + node: { + databaseId: 2217789, + createdAt: '2022-02-21T03:30:42Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2223243, - createdAt: '2022-02-21T18:26:27Z', - replies: { - edges: [ - { - node: { - databaseId: 2232922, - createdAt: '2022-02-23T00:57:58Z', + { + node: { + databaseId: 2223243, + createdAt: '2022-02-21T18:26:27Z', + replies: { + edges: [ + { + node: { + databaseId: 2232922, + createdAt: '2022-02-23T00:57:58Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2232921, - createdAt: '2022-02-23T00:57:49Z', - replies: { - edges: [], + { + node: { + databaseId: 2232921, + createdAt: '2022-02-23T00:57:49Z', + replies: { + edges: [], + }, }, }, - }, - { - node: { - databaseId: 2258799, - createdAt: '2022-02-27T01:22:20Z', - replies: { - edges: [ - { - node: { - databaseId: 2300902, - createdAt: '2022-03-05T17:43:52Z', + { + node: { + databaseId: 2258799, + createdAt: '2022-02-27T01:22:20Z', + replies: { + edges: [ + { + node: { + databaseId: 2300902, + createdAt: '2022-03-05T17:43:52Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2297637, - createdAt: '2022-03-04T20:39:44Z', - replies: { - edges: [ - { - node: { - databaseId: 2300893, - createdAt: '2022-03-05T17:41:04Z', + { + node: { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + replies: { + edges: [ + { + node: { + databaseId: 2300893, + createdAt: '2022-03-05T17:41:04Z', + }, }, - }, - ], + ], + }, }, }, - }, - { - node: { - databaseId: 2299763, - createdAt: '2022-03-05T11:05:42Z', - replies: { - edges: [ - { - node: { - databaseId: 2300895, - createdAt: '2022-03-05T17:41:44Z', + { + node: { + databaseId: 2299763, + createdAt: '2022-03-05T11:05:42Z', + replies: { + edges: [ + { + node: { + databaseId: 2300895, + createdAt: '2022-03-05T17:41:44Z', + }, }, - }, - ], + ], + }, }, }, - }, - ], + ], + }, }, }, - }, - ], + ], + }, }, }, + }; + +export const mockedDiscussionNotifications = [ + { + id: 1, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is an answered discussion', + type: 'Discussion', + }, }, -}; + { + id: 2, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is a duplicate discussion', + type: 'Discussion', + }, + }, + { + id: 3, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is an open discussion', + type: 'Discussion', + }, + }, + { + id: 4, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is nm outdated discussion', + type: 'Discussion', + }, + }, + { + id: 5, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is a reopened discussion', + type: 'Discussion', + }, + }, + { + id: 6, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is a resolved discussion', + type: 'Discussion', + }, + }, + { + id: 7, + updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'some/repo', + }, + subject: { + title: 'This is a default discussion', + type: 'Discussion', + }, + }, +]; diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 2d133363f..4285d006a 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -3,7 +3,10 @@ import axios from 'axios'; import nock from 'nock'; import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; -import { mockedUser } from '../__mocks__/mockedData'; +import { + mockedDiscussionNotifications, + mockedUser, +} from '../__mocks__/mockedData'; import { AuthState } from '../types'; import { useNotifications } from './useNotifications'; @@ -191,28 +194,43 @@ describe('hooks/useNotifications.ts', () => { const notifications = [ { id: 1, - title: 'This is a notification.', - subject: { type: 'Issue', url: 'https://api.github.com/1' }, + subject: { + title: 'This is a notification.', + type: 'Issue', + url: 'https://api.github.com/1', + }, }, { id: 2, - title: 'A merged PR.', - subject: { type: 'PullRequest', url: 'https://api.github.com/2' }, + subject: { + title: 'A merged PR.', + type: 'PullRequest', + url: 'https://api.github.com/2', + }, }, { id: 3, - title: 'A closed PR.', - subject: { type: 'PullRequest', url: 'https://api.github.com/3' }, + subject: { + title: 'A closed PR.', + type: 'PullRequest', + url: 'https://api.github.com/3', + }, }, { id: 4, - title: 'A draft PR.', - subject: { type: 'PullRequest', url: 'https://api.github.com/4' }, + subject: { + title: 'A draft PR.', + type: 'PullRequest', + url: 'https://api.github.com/4', + }, }, { id: 5, - title: 'A draft PR.', - subject: { type: 'PullRequest', url: 'https://api.github.com/5' }, + subject: { + title: 'A draft PR.', + type: 'PullRequest', + url: 'https://api.github.com/5', + }, }, ]; @@ -266,6 +284,183 @@ describe('hooks/useNotifications.ts', () => { result.current.notifications[0].notifications[4].subject.state, ).toBe('draft'); }); + + it('should fetch discussion notifications with success - with colors', async () => { + const accounts: AuthState = { + ...mockAccounts, + enterpriseAccounts: [], + user: mockedUser, + }; + + nock('https://api.github.com') + .get('/notifications?participating=false') + .reply(200, mockedDiscussionNotifications); + + nock('https://api.github.com') + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is an answered discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: true, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is a duplicate discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'DUPLICATE', + isAnswered: false, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is an open discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: false, + }, + }, + { + node: { + title: 'This is an open discussion', + viewerSubscription: 'IGNORED', + stateReason: null, + isAnswered: false, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is nm outdated discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'OUTDATED', + isAnswered: false, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is a reopened discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'REOPENED', + isAnswered: false, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'This is a resolved discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'RESOLVED', + isAnswered: false, + }, + }, + ], + }, + }, + }) + .post('/graphql') + .reply(200, { + data: { + search: { + edges: [ + { + node: { + title: 'unknown search result', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: false, + }, + }, + ], + }, + }, + }); + + const { result } = renderHook(() => useNotifications(true)); + + act(() => { + result.current.fetchNotifications(accounts, { + ...mockSettings, + colors: true, + }); + }); + + expect(result.current.isFetching).toBe(true); + + await waitFor(() => { + expect(result.current.notifications[0].hostname).toBe('github.com'); + }); + + const resultNotifications = result.current.notifications[0]; + + expect(resultNotifications.notifications.length).toBe(7); + expect(resultNotifications.notifications[0].subject.state).toBe( + 'ANSWERED', + ); + expect(resultNotifications.notifications[1].subject.state).toBe( + 'DUPLICATE', + ); + expect(resultNotifications.notifications[2].subject.state).toBe('OPEN'); + expect(resultNotifications.notifications[3].subject.state).toBe( + 'OUTDATED', + ); + expect(resultNotifications.notifications[4].subject.state).toBe( + 'REOPENED', + ); + expect(resultNotifications.notifications[5].subject.state).toBe( + 'RESOLVED', + ); + expect(resultNotifications.notifications[6].subject.state).toBe('OPEN'); + }); }); }); diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 7ad405149..4d875a44a 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -9,6 +9,7 @@ import { getEnterpriseAccountToken, generateGitHubAPIUrl, isEnterpriseHost, + getDiscussionState, } from '../utils/helpers'; import { removeNotification } from '../utils/remove-notification'; import { @@ -130,12 +131,6 @@ export const useNotifications = (colors: boolean): NotificationsState => { notifications: await axios.all( accountNotifications.notifications.map( async (notification: Notification) => { - if ( - notification.subject.type !== 'PullRequest' && - notification.subject.type !== 'Issue' - ) { - return notification; - } const isEnterprise = isEnterpriseHost( accountNotifications.hostname, ); @@ -146,28 +141,47 @@ export const useNotifications = (colors: boolean): NotificationsState => { ) : accounts.token; - const cardinalData = ( - await apiRequestAuth( - notification.subject.url, - 'GET', - token, - ) - ).data; - - const state = - cardinalData.state === 'closed' - ? cardinalData.state_reason || - (cardinalData.merged && 'merged') || - 'closed' - : (cardinalData.draft && 'draft') || 'open'; - - return { - ...notification, - subject: { - ...notification.subject, - state, - }, - }; + switch (notification.subject.type) { + case 'Discussion': + const discussionState = await getDiscussionState( + notification, + token, + ); + + return { + ...notification, + subject: { + ...notification.subject, + state: discussionState, + }, + }; + case 'Issue': + case 'PullRequest': + const cardinalData = ( + await apiRequestAuth( + notification.subject.url, + 'GET', + token, + ) + ).data; + + const state = + cardinalData.state === 'closed' + ? cardinalData.state_reason || + (cardinalData.merged && 'merged') || + 'closed' + : (cardinalData.draft && 'draft') || 'open'; + + return { + ...notification, + subject: { + ...notification.subject, + state, + }, + }; + default: + return notification; + } }, ), ), diff --git a/src/typesGithub.ts b/src/typesGithub.ts index 61bda5b30..d7b18de74 100644 --- a/src/typesGithub.ts +++ b/src/typesGithub.ts @@ -15,6 +15,15 @@ export type Reason = | 'subscribed' | 'team_mention'; +// Note: ANSWERED and OPEN are not an official discussion state type in the GitHub API +export type DiscussionStateType = + | 'ANSWERED' + | 'DUPLICATE' + | 'OPEN' + | 'OUTDATED' + | 'REOPENED' + | 'RESOLVED'; + export type SubjectType = | 'CheckSuite' | 'Commit' @@ -35,7 +44,10 @@ export type IssueStateType = export type PullRequestStateType = 'closed' | 'draft' | 'merged' | 'open'; -export type StateType = IssueStateType | PullRequestStateType; +export type StateType = + | DiscussionStateType + | IssueStateType + | PullRequestStateType; export type ViewerSubscription = 'IGNORED' | 'SUBSCRIBED' | 'UNSUBSCRIBED'; @@ -145,22 +157,31 @@ export interface Owner { export interface Subject { title: string; url: string | null; - state?: StateType; + state?: StateType; // This is not in the GitHub API, but we add it to the type to make it easier to work with latest_comment_url: string | null; type: SubjectType; } -export interface GraphQLSearch { +export interface GraphQLSearch { data: { data: { search: { - edges: DiscussionEdge[]; + edges: T[]; }; }; }; } -export interface DiscussionEdge { +export interface DiscussionStateSearchResultEdge { + node: { + viewerSubscription: ViewerSubscription; + title: string; + stateReason: DiscussionStateType; + isAnswered: boolean; + }; +} + +export interface DiscussionSearchResultEdge { node: { viewerSubscription: ViewerSubscription; title: string; diff --git a/src/utils/__snapshots__/github-api.test.ts.snap b/src/utils/__snapshots__/github-api.test.ts.snap index e6b226300..0b472531e 100644 --- a/src/utils/__snapshots__/github-api.test.ts.snap +++ b/src/utils/__snapshots__/github-api.test.ts.snap @@ -122,18 +122,22 @@ exports[`getNotificationTypeIconColor should format the notification color for c exports[`getNotificationTypeIconColor should format the notification color for check suite 5`] = `"text-gray-300"`; -exports[`getNotificationTypeIconColor should format the notification color for state 1`] = `"text-red-500"`; +exports[`getNotificationTypeIconColor should format the notification color for state 1`] = `"text-green-500"`; -exports[`getNotificationTypeIconColor should format the notification color for state 2`] = `"text-purple-500"`; +exports[`getNotificationTypeIconColor should format the notification color for state 2`] = `"text-red-500"`; -exports[`getNotificationTypeIconColor should format the notification color for state 3`] = `"text-gray-600"`; +exports[`getNotificationTypeIconColor should format the notification color for state 3`] = `"text-purple-500"`; -exports[`getNotificationTypeIconColor should format the notification color for state 4`] = `"text-purple-500"`; +exports[`getNotificationTypeIconColor should format the notification color for state 4`] = `"text-gray-600"`; -exports[`getNotificationTypeIconColor should format the notification color for state 5`] = `"text-gray-300"`; +exports[`getNotificationTypeIconColor should format the notification color for state 5`] = `"text-purple-500"`; -exports[`getNotificationTypeIconColor should format the notification color for state 6`] = `"text-green-500"`; +exports[`getNotificationTypeIconColor should format the notification color for state 6`] = `"text-gray-300"`; exports[`getNotificationTypeIconColor should format the notification color for state 7`] = `"text-green-500"`; -exports[`getNotificationTypeIconColor should format the notification color for state 8`] = `"text-gray-300"`; +exports[`getNotificationTypeIconColor should format the notification color for state 8`] = `"text-green-500"`; + +exports[`getNotificationTypeIconColor should format the notification color for state 9`] = `"text-purple-500"`; + +exports[`getNotificationTypeIconColor should format the notification color for state 10`] = `"text-gray-300"`; diff --git a/src/utils/github-api.test.ts b/src/utils/github-api.test.ts index d3a335b2a..6e000f22b 100644 --- a/src/utils/github-api.test.ts +++ b/src/utils/github-api.test.ts @@ -72,6 +72,21 @@ describe('getNotificationTypeIcon', () => { getNotificationTypeIcon(createSubjectMock({ type: 'Discussion' })) .displayName, ).toBe('CommentDiscussionIcon'); + expect( + getNotificationTypeIcon( + createSubjectMock({ type: 'Discussion', state: 'DUPLICATE' }), + ).displayName, + ).toBe('DiscussionDuplicateIcon'); + expect( + getNotificationTypeIcon( + createSubjectMock({ type: 'Discussion', state: 'OUTDATED' }), + ).displayName, + ).toBe('DiscussionOutdatedIcon'); + expect( + getNotificationTypeIcon( + createSubjectMock({ type: 'Discussion', state: 'RESOLVED' }), + ).displayName, + ).toBe('DiscussionClosedIcon'); expect( getNotificationTypeIcon(createSubjectMock({ type: 'Issue' })).displayName, ).toBe('IssueOpenedIcon'); @@ -220,6 +235,9 @@ describe('getNotificationTypeIconColor', () => { }); it('should format the notification color for state', () => { + expect( + getNotificationTypeIconColor(createSubjectMock({ state: 'ANSWERED' })), + ).toMatchSnapshot(); expect( getNotificationTypeIconColor(createSubjectMock({ state: 'closed' })), ).toMatchSnapshot(); @@ -241,6 +259,9 @@ describe('getNotificationTypeIconColor', () => { expect( getNotificationTypeIconColor(createSubjectMock({ state: 'reopened' })), ).toMatchSnapshot(); + expect( + getNotificationTypeIconColor(createSubjectMock({ state: 'RESOLVED' })), + ).toMatchSnapshot(); expect( getNotificationTypeIconColor( createSubjectMock({ diff --git a/src/utils/github-api.ts b/src/utils/github-api.ts index 0e3789904..eb2a64440 100644 --- a/src/utils/github-api.ts +++ b/src/utils/github-api.ts @@ -2,6 +2,9 @@ import { AlertIcon, CheckIcon, CommentDiscussionIcon, + DiscussionClosedIcon, + DiscussionDuplicateIcon, + DiscussionOutdatedIcon, GitCommitIcon, GitMergeIcon, GitPullRequestClosedIcon, @@ -106,7 +109,16 @@ export function getNotificationTypeIcon( case 'Commit': return GitCommitIcon; case 'Discussion': - return CommentDiscussionIcon; + switch (subject.state) { + case 'DUPLICATE': + return DiscussionDuplicateIcon; + case 'OUTDATED': + return DiscussionOutdatedIcon; + case 'RESOLVED': + return DiscussionClosedIcon; + default: + return CommentDiscussionIcon; + } case 'Issue': switch (subject.state) { case 'draft': @@ -164,6 +176,8 @@ export function getNotificationTypeIconColor(subject: Subject): string { } switch (subject.state) { + case 'ANSWERED': + return 'text-green-500'; case 'closed': return 'text-red-500'; case 'completed': @@ -178,6 +192,8 @@ export function getNotificationTypeIconColor(subject: Subject): string { return 'text-green-500'; case 'reopened': return 'text-green-500'; + case 'RESOLVED': + return 'text-purple-500'; default: return 'text-gray-300'; } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 48cd13476..d343f7b13 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -3,6 +3,9 @@ import { Notification, GraphQLSearch, DiscussionCommentEdge, + DiscussionStateType, + DiscussionStateSearchResultEdge, + DiscussionSearchResultEdge, } from '../typesGithub'; import { apiRequestAuth } from '../utils/api-requests'; import { openExternalLink } from '../utils/comms'; @@ -75,11 +78,8 @@ async function getDiscussionUrl( ): Promise { let url = `${notification.repository.html_url}/discussions`; - const response: GraphQLSearch = await apiRequestAuth( - `https://api.github.com/graphql`, - 'POST', - token, - { + const response: GraphQLSearch = + await apiRequestAuth(`https://api.github.com/graphql`, 'POST', token, { query: `{ search(query:"${formatSearchQueryString( notification.repository.full_name, @@ -92,6 +92,7 @@ async function getDiscussionUrl( viewerSubscription title url + stateReason comments(last: 100) { edges { node { @@ -113,8 +114,7 @@ async function getDiscussionUrl( } } }`, - }, - ); + }); let edges = response?.data?.data?.search?.edges?.filter( (edge) => edge.node.title === notification.subject.title, @@ -139,6 +139,53 @@ async function getDiscussionUrl( return url; } +export async function getDiscussionState( + notification: Notification, + token: string, +): Promise { + const response: GraphQLSearch = + await apiRequestAuth(`https://api.github.com/graphql`, 'POST', token, { + query: `{ + search(query:"${formatSearchQueryString( + notification.repository.full_name, + notification.subject.title, + notification.updated_at, + )}", type: DISCUSSION, first: 10) { + edges { + node { + ... on Discussion { + viewerSubscription + title + stateReason + isAnswered + } + } + } + } + }`, + }); + let edges = + response?.data?.data?.search?.edges?.filter( + (edge) => edge.node.title === notification.subject.title, + ) || []; + if (edges.length > 1) + edges = edges.filter( + (edge) => edge.node.viewerSubscription === 'SUBSCRIBED', + ); + + if (edges[0]) { + if (edges[0].node.isAnswered) { + return 'ANSWERED'; + } + + if (edges[0].node.stateReason) { + return edges[0].node.stateReason; + } + } + + return 'OPEN'; +} + export const getLatestDiscussionCommentId = ( comments: DiscussionCommentEdge[], ) =>