From 56d4bd0cdf1fe85e101bf9be8ff5fa9ae31a03fd Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 7 Mar 2024 07:39:41 -0500 Subject: [PATCH 1/5] refactor: graphql queries use nodes --- src/__mocks__/mockedData.ts | 310 +++++++++++++++--------------------- src/typesGithub.ts | 48 +++--- src/utils/helpers.ts | 86 +++++----- src/utils/state.test.ts | 94 +++++------ src/utils/state.ts | 53 +++--- 5 files changed, 257 insertions(+), 334 deletions(-) diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index cfa1ef9bc..3fedecffe 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -4,7 +4,7 @@ import { Repository, User, GraphQLSearch, - DiscussionSearchResultEdge, + DiscussionSearchResultNode, } from '../typesGithub'; export const mockedEnterpriseAccounts: EnterpriseAccount[] = [ @@ -282,214 +282,166 @@ export const mockedSingleAccountNotifications: AccountNotifications[] = [ }, ]; -export const mockedGraphQLResponse: GraphQLSearch = +export const mockedGraphQLResponse: GraphQLSearch = { data: { data: { search: { - edges: [ + nodes: [ { - 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: [], - }, - }, + viewerSubscription: 'SUBSCRIBED', + title: '1.16.0', + url: 'https://github.com/manosim/notifications-test/discussions/612', + comments: { + nodes: [ + { + databaseId: 2215656, + createdAt: '2022-02-20T18:33:39Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2217789, - createdAt: '2022-02-21T03:30:42Z', - replies: { - edges: [], - }, - }, + }, + { + databaseId: 2217789, + createdAt: '2022-02-21T03:30:42Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2223243, - createdAt: '2022-02-21T18:26:27Z', - replies: { - edges: [ - { - node: { - databaseId: 2232922, - createdAt: '2022-02-23T00:57:58Z', - }, - }, - ], + }, + { + databaseId: 2223243, + createdAt: '2022-02-21T18:26:27Z', + replies: { + nodes: [ + { + databaseId: 2232922, + createdAt: '2022-02-23T00:57:58Z', }, - }, + ], }, - { - node: { - databaseId: 2232921, - createdAt: '2022-02-23T00:57:49Z', - replies: { - edges: [], - }, - }, + }, + { + databaseId: 2232921, + createdAt: '2022-02-23T00:57:49Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2258799, - createdAt: '2022-02-27T01:22:20Z', - replies: { - edges: [ - { - node: { - databaseId: 2300902, - createdAt: '2022-03-05T17:43:52Z', - }, - }, - ], + }, + { + databaseId: 2258799, + createdAt: '2022-02-27T01:22:20Z', + replies: { + nodes: [ + { + 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', - }, - }, - ], + }, + { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + replies: { + nodes: [ + { + 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', - }, - }, - ], + }, + { + databaseId: 2299763, + createdAt: '2022-03-05T11:05:42Z', + replies: { + nodes: [ + { + 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: [], - }, - }, + viewerSubscription: 'IGNORED', + title: '1.16.0', + url: 'https://github.com/manosim/notifications-test/discussions/612', + comments: { + nodes: [ + { + databaseId: 2215656, + createdAt: '2022-02-20T18:33:39Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2217789, - createdAt: '2022-02-21T03:30:42Z', - replies: { - edges: [], - }, - }, + }, + { + databaseId: 2217789, + createdAt: '2022-02-21T03:30:42Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2223243, - createdAt: '2022-02-21T18:26:27Z', - replies: { - edges: [ - { - node: { - databaseId: 2232922, - createdAt: '2022-02-23T00:57:58Z', - }, - }, - ], + }, + { + databaseId: 2223243, + createdAt: '2022-02-21T18:26:27Z', + replies: { + nodes: [ + { + databaseId: 2232922, + createdAt: '2022-02-23T00:57:58Z', }, - }, + ], }, - { - node: { - databaseId: 2232921, - createdAt: '2022-02-23T00:57:49Z', - replies: { - edges: [], - }, - }, + }, + { + databaseId: 2232921, + createdAt: '2022-02-23T00:57:49Z', + replies: { + nodes: [], }, - { - node: { - databaseId: 2258799, - createdAt: '2022-02-27T01:22:20Z', - replies: { - edges: [ - { - node: { - databaseId: 2300902, - createdAt: '2022-03-05T17:43:52Z', - }, - }, - ], + }, + { + databaseId: 2258799, + createdAt: '2022-02-27T01:22:20Z', + replies: { + nodes: [ + { + 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', - }, - }, - ], + }, + { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + replies: { + nodes: [ + { + 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', - }, - }, - ], + }, + { + databaseId: 2299763, + createdAt: '2022-03-05T11:05:42Z', + replies: { + nodes: [ + { + databaseId: 2300895, + createdAt: '2022-03-05T17:41:44Z', }, - }, + ], }, - ], - }, + }, + ], }, }, ], diff --git a/src/typesGithub.ts b/src/typesGithub.ts index d051ab674..2f46d0153 100644 --- a/src/typesGithub.ts +++ b/src/typesGithub.ts @@ -169,47 +169,39 @@ export interface GraphQLSearch { data: { data: { search: { - edges: T[]; + nodes: T[]; }; }; }; } -export interface DiscussionStateSearchResultEdge { - node: { - viewerSubscription: ViewerSubscription; - title: string; - stateReason: DiscussionStateType; - isAnswered: boolean; - }; +export interface DiscussionStateSearchResultNode { + viewerSubscription: ViewerSubscription; + title: string; + stateReason: DiscussionStateType; + isAnswered: boolean; } -export interface DiscussionSearchResultEdge { - node: { - viewerSubscription: ViewerSubscription; - title: string; - url: string; - comments: { - edges: DiscussionCommentEdge[]; - }; +export interface DiscussionSearchResultNode { + viewerSubscription: ViewerSubscription; + title: string; + url: string; + comments: { + nodes: DiscussionCommentNode[]; }; } -export interface DiscussionCommentEdge { - node: { - databaseId: string | number; - createdAt: string; - replies: { - edges: DiscussionSubcommentEdge[]; - }; +export interface DiscussionCommentNode { + databaseId: string | number; + createdAt: string; + replies: { + nodes: DiscussionSubcommentNode[]; }; } -export interface DiscussionSubcommentEdge { - node: { - databaseId: string | number; - createdAt: string; - }; +export interface DiscussionSubcommentNode { + databaseId: string | number; + createdAt: string; } export interface CheckSuiteAttributes { diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 2ccdbe9b4..15153cd46 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -2,8 +2,8 @@ import { EnterpriseAccount, AuthState } from '../types'; import { Notification, GraphQLSearch, - DiscussionCommentEdge, - DiscussionSearchResultEdge, + DiscussionCommentNode, + DiscussionSearchResultNode, } from '../typesGithub'; import { apiRequestAuth } from '../utils/api-requests'; import { openExternalLink } from '../utils/comms'; @@ -76,55 +76,52 @@ async function getDiscussionUrl( ): Promise { let url = `${notification.repository.html_url}/discussions`; - const response: GraphQLSearch = + 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 - url - comments(last: 100) { - edges { - node { - databaseId - createdAt - replies(last: 1) { - edges { - node { - databaseId - createdAt - } - } - } - } - } - } + search(query:"${formatSearchQueryString( + notification.repository.full_name, + notification.subject.title, + notification.updated_at, + )}", type: DISCUSSION, first: 10) { + nodes { + ... on Discussion { + viewerSubscription + title + url + comments(last: 100) { + nodes { + databaseId + createdAt + replies(last: 1) { + nodes { + databaseId + createdAt + } } + } } + } } - } - }`, + } + }`, }); - let edges = - response?.data?.data?.search?.edges?.filter( - (edge) => edge.node.title === notification.subject.title, + + let discussions = + response?.data?.data?.search?.nodes?.filter( + (discussion) => discussion.title === notification.subject.title, ) || []; - if (edges.length > 1) - edges = edges.filter( - (edge) => edge.node.viewerSubscription === 'SUBSCRIBED', + + if (discussions.length > 1) + discussions = discussions.filter( + (discussion) => discussion.viewerSubscription === 'SUBSCRIBED', ); - if (edges[0]) { - url = edges[0].node.url; + if (discussions[0]) { + const discussion = discussions[0]; + url = discussion.url; - let comments = edges[0]?.node.comments.edges; + let comments = discussion.comments.nodes; let latestCommentId: string | number; if (comments?.length) { @@ -137,13 +134,12 @@ async function getDiscussionUrl( } export const getLatestDiscussionCommentId = ( - comments: DiscussionCommentEdge[], + comments: DiscussionCommentNode[], ) => comments - .flatMap((comment) => comment.node.replies.edges) + .flatMap((comment) => comment.replies.nodes) .concat([comments.at(-1)]) - .reduce((a, b) => (a.node.createdAt > b.node.createdAt ? a : b))?.node - .databaseId; + .reduce((a, b) => (a.createdAt > b.createdAt ? a : b))?.databaseId; export async function generateGitHubWebUrl( notification: Notification, diff --git a/src/utils/state.test.ts b/src/utils/state.test.ts index 0335dde12..89a62318b 100644 --- a/src/utils/state.test.ts +++ b/src/utils/state.test.ts @@ -150,14 +150,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is an answered discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: null, - isAnswered: true, - }, + title: 'This is an answered discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: true, }, ], }, @@ -186,14 +184,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is a duplicate discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: 'DUPLICATE', - isAnswered: false, - }, + title: 'This is a duplicate discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'DUPLICATE', + isAnswered: false, }, ], }, @@ -222,14 +218,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is an open discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: null, - isAnswered: false, - }, + title: 'This is an open discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: false, }, ], }, @@ -258,14 +252,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is an outdated discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: 'OUTDATED', - isAnswered: false, - }, + title: 'This is an outdated discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'OUTDATED', + isAnswered: false, }, ], }, @@ -294,14 +286,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is a reopened discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: 'REOPENED', - isAnswered: false, - }, + title: 'This is a reopened discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'REOPENED', + isAnswered: false, }, ], }, @@ -330,14 +320,12 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is a resolved discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: 'RESOLVED', - isAnswered: false, - }, + title: 'This is a resolved discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: 'RESOLVED', + isAnswered: false, }, ], }, @@ -366,22 +354,18 @@ describe('utils/state.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is a discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: null, - isAnswered: false, - }, + title: 'This is a discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: false, }, { - node: { - title: 'This is a discussion', - viewerSubscription: 'IGNORED', - stateReason: null, - isAnswered: true, - }, + title: 'This is a discussion', + viewerSubscription: 'IGNORED', + stateReason: null, + isAnswered: true, }, ], }, diff --git a/src/utils/state.ts b/src/utils/state.ts index 4d2b79c61..ad783e894 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -2,7 +2,7 @@ import { formatSearchQueryString } from './helpers'; import { CheckSuiteAttributes, CheckSuiteStatus, - DiscussionStateSearchResultEdge, + DiscussionStateSearchResultNode, DiscussionStateType, GraphQLSearch, IssueStateType, @@ -81,46 +81,45 @@ export async function getDiscussionState( notification: Notification, token: string, ): Promise { - const response: GraphQLSearch = + 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 - } - } + search(query:"${formatSearchQueryString( + notification.repository.full_name, + notification.subject.title, + notification.updated_at, + )}", type: DISCUSSION, first: 10) { + nodes { + ... on Discussion { + viewerSubscription + title + stateReason + isAnswered } } - }`, + } + }`, }); - let edges = - response?.data?.data?.search?.edges?.filter( - (edge) => edge.node.title === notification.subject.title, + let discussions = + response?.data?.data?.search?.nodes?.filter( + (discussion) => discussion.title === notification.subject.title, ) || []; - if (edges.length > 1) { - edges = edges.filter( - (edge) => edge.node.viewerSubscription === 'SUBSCRIBED', + if (discussions.length > 1) { + discussions = discussions.filter( + (discussion) => discussion.viewerSubscription === 'SUBSCRIBED', ); } - if (edges[0]) { - if (edges[0].node.isAnswered) { + if (discussions[0]) { + const discussion = discussions[0]; + if (discussion.isAnswered) { return 'ANSWERED'; } - if (edges[0].node.stateReason) { - return edges[0].node.stateReason; + if (discussion.stateReason) { + return discussion.stateReason; } } From 25b8d7d4caa73bef24a94347a99abf19ece76d23 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 7 Mar 2024 09:51:11 -0500 Subject: [PATCH 2/5] refactor: combine discussion graphql, use query variables and include directive --- src/__mocks__/mockedData.ts | 4 ++ src/typesGithub.ts | 7 +-- src/utils/helpers.ts | 91 ++++++++++++++++++++++++------------- src/utils/state.ts | 38 ++-------------- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index 3fedecffe..59f046f34 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -291,6 +291,8 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'SUBSCRIBED', title: '1.16.0', + isAnswered: false, + stateReason: null, url: 'https://github.com/manosim/notifications-test/discussions/612', comments: { nodes: [ @@ -369,6 +371,8 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'IGNORED', title: '1.16.0', + isAnswered: false, + stateReason: null, url: 'https://github.com/manosim/notifications-test/discussions/612', comments: { nodes: [ diff --git a/src/typesGithub.ts b/src/typesGithub.ts index 2f46d0153..36ee5e103 100644 --- a/src/typesGithub.ts +++ b/src/typesGithub.ts @@ -175,16 +175,11 @@ export interface GraphQLSearch { }; } -export interface DiscussionStateSearchResultNode { +export interface DiscussionSearchResultNode { viewerSubscription: ViewerSubscription; title: string; stateReason: DiscussionStateType; isAnswered: boolean; -} - -export interface DiscussionSearchResultNode { - viewerSubscription: ViewerSubscription; - title: string; url: string; comments: { nodes: DiscussionCommentNode[]; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 15153cd46..492a978df 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -76,27 +76,53 @@ async function getDiscussionUrl( ): Promise { let url = `${notification.repository.html_url}/discussions`; + const discussion = await fetchDiscussion(notification, token, true); + + if (discussion) { + url = discussion.url; + + let comments = discussion.comments.nodes; + + let latestCommentId: string | number; + if (comments?.length) { + latestCommentId = getLatestDiscussionCommentId(comments); + url += `#discussioncomment-${latestCommentId}`; + } + } + + return url; +} + +export async function fetchDiscussion( + notification: Notification, + token: string, + includeComments: boolean, +): 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) { - nodes { - ... on Discussion { - viewerSubscription - title - url - comments(last: 100) { - nodes { - databaseId - createdAt - replies(last: 1) { - nodes { - databaseId - createdAt + query: `query fetchDiscussions( + $queryStatement: String!, + $type: SearchType!, + $firstDiscussions: Int, + $lastComments: Int, + $includeComments: Boolean, + $lastReplies: Int + ) { + search(query:$queryStatement, type: $type, first: $firstDiscussions) { + nodes { + ... on Discussion { + viewerSubscription + title + url + comments(last: $lastComments) @include(if: $includeComments){ + nodes { + databaseId + createdAt + replies(last: $firstReplies) { + nodes { + databaseId + createdAt + } } } } @@ -104,7 +130,19 @@ async function getDiscussionUrl( } } } - }`, + `, + variables: { + queryStatement: formatSearchQueryString( + notification.repository.full_name, + notification.subject.title, + notification.updated_at, + ), + type: 'DISCUSSION', + firstDiscussions: 10, + lastComments: 100, + firstReplies: 1, + includeComments: includeComments, + }, }); let discussions = @@ -118,19 +156,10 @@ async function getDiscussionUrl( ); if (discussions[0]) { - const discussion = discussions[0]; - url = discussion.url; - - let comments = discussion.comments.nodes; - - let latestCommentId: string | number; - if (comments?.length) { - latestCommentId = getLatestDiscussionCommentId(comments); - url += `#discussioncomment-${latestCommentId}`; - } + return discussions[0]; } - return url; + return null; } export const getLatestDiscussionCommentId = ( diff --git a/src/utils/state.ts b/src/utils/state.ts index ad783e894..b608b6f36 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -1,10 +1,8 @@ -import { formatSearchQueryString } from './helpers'; +import { fetchDiscussion } from './helpers'; import { CheckSuiteAttributes, CheckSuiteStatus, - DiscussionStateSearchResultNode, DiscussionStateType, - GraphQLSearch, IssueStateType, Notification, PullRequestStateType, @@ -81,39 +79,9 @@ 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) { - nodes { - ... on Discussion { - viewerSubscription - title - stateReason - isAnswered - } - } - } - }`, - }); - - let discussions = - response?.data?.data?.search?.nodes?.filter( - (discussion) => discussion.title === notification.subject.title, - ) || []; - - if (discussions.length > 1) { - discussions = discussions.filter( - (discussion) => discussion.viewerSubscription === 'SUBSCRIBED', - ); - } + const discussion = await fetchDiscussion(notification, token, false); - if (discussions[0]) { - const discussion = discussions[0]; + if (discussion) { if (discussion.isAnswered) { return 'ANSWERED'; } From e3c6d9cf7f63715d3be27b685c26033265f5a95d Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 7 Mar 2024 09:51:11 -0500 Subject: [PATCH 3/5] refactor: combine discussion graphql, use query variables and include directive --- src/__mocks__/mockedData.ts | 4 ++ src/typesGithub.ts | 7 +-- src/utils/helpers.ts | 91 ++++++++++++++++++++++++------------- src/utils/state.ts | 38 ++-------------- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index 3fedecffe..59f046f34 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -291,6 +291,8 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'SUBSCRIBED', title: '1.16.0', + isAnswered: false, + stateReason: null, url: 'https://github.com/manosim/notifications-test/discussions/612', comments: { nodes: [ @@ -369,6 +371,8 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'IGNORED', title: '1.16.0', + isAnswered: false, + stateReason: null, url: 'https://github.com/manosim/notifications-test/discussions/612', comments: { nodes: [ diff --git a/src/typesGithub.ts b/src/typesGithub.ts index 2f46d0153..36ee5e103 100644 --- a/src/typesGithub.ts +++ b/src/typesGithub.ts @@ -175,16 +175,11 @@ export interface GraphQLSearch { }; } -export interface DiscussionStateSearchResultNode { +export interface DiscussionSearchResultNode { viewerSubscription: ViewerSubscription; title: string; stateReason: DiscussionStateType; isAnswered: boolean; -} - -export interface DiscussionSearchResultNode { - viewerSubscription: ViewerSubscription; - title: string; url: string; comments: { nodes: DiscussionCommentNode[]; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 322fb6286..a2f62fead 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -121,27 +121,53 @@ async function getDiscussionUrl( ): Promise { let url = `${notification.repository.html_url}/discussions`; + const discussion = await fetchDiscussion(notification, token, true); + + if (discussion) { + url = discussion.url; + + let comments = discussion.comments.nodes; + + let latestCommentId: string | number; + if (comments?.length) { + latestCommentId = getLatestDiscussionCommentId(comments); + url += `#discussioncomment-${latestCommentId}`; + } + } + + return url; +} + +export async function fetchDiscussion( + notification: Notification, + token: string, + includeComments: boolean, +): 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) { - nodes { - ... on Discussion { - viewerSubscription - title - url - comments(last: 100) { - nodes { - databaseId - createdAt - replies(last: 1) { - nodes { - databaseId - createdAt + query: `query fetchDiscussions( + $queryStatement: String!, + $type: SearchType!, + $firstDiscussions: Int, + $lastComments: Int, + $includeComments: Boolean, + $lastReplies: Int + ) { + search(query:$queryStatement, type: $type, first: $firstDiscussions) { + nodes { + ... on Discussion { + viewerSubscription + title + url + comments(last: $lastComments) @include(if: $includeComments){ + nodes { + databaseId + createdAt + replies(last: $firstReplies) { + nodes { + databaseId + createdAt + } } } } @@ -149,7 +175,19 @@ async function getDiscussionUrl( } } } - }`, + `, + variables: { + queryStatement: formatSearchQueryString( + notification.repository.full_name, + notification.subject.title, + notification.updated_at, + ), + type: 'DISCUSSION', + firstDiscussions: 10, + lastComments: 100, + firstReplies: 1, + includeComments: includeComments, + }, }); let discussions = @@ -163,19 +201,10 @@ async function getDiscussionUrl( ); if (discussions[0]) { - const discussion = discussions[0]; - url = discussion.url; - - let comments = discussion.comments.nodes; - - let latestCommentId: string | number; - if (comments?.length) { - latestCommentId = getLatestDiscussionCommentId(comments); - url += `#discussioncomment-${latestCommentId}`; - } + return discussions[0]; } - return url; + return null; } export const getLatestDiscussionCommentId = ( diff --git a/src/utils/state.ts b/src/utils/state.ts index ad783e894..b608b6f36 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -1,10 +1,8 @@ -import { formatSearchQueryString } from './helpers'; +import { fetchDiscussion } from './helpers'; import { CheckSuiteAttributes, CheckSuiteStatus, - DiscussionStateSearchResultNode, DiscussionStateType, - GraphQLSearch, IssueStateType, Notification, PullRequestStateType, @@ -81,39 +79,9 @@ 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) { - nodes { - ... on Discussion { - viewerSubscription - title - stateReason - isAnswered - } - } - } - }`, - }); - - let discussions = - response?.data?.data?.search?.nodes?.filter( - (discussion) => discussion.title === notification.subject.title, - ) || []; - - if (discussions.length > 1) { - discussions = discussions.filter( - (discussion) => discussion.viewerSubscription === 'SUBSCRIBED', - ); - } + const discussion = await fetchDiscussion(notification, token, false); - if (discussions[0]) { - const discussion = discussions[0]; + if (discussion) { if (discussion.isAnswered) { return 'ANSWERED'; } From 083765c399f0c8c350bde9b0e38fe2f0d51c8fdc Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 7 Mar 2024 11:44:16 -0500 Subject: [PATCH 4/5] refactor: graphql query --- src/utils/helpers.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index a2f62fead..ea6db2eea 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -129,6 +129,7 @@ async function getDiscussionUrl( let comments = discussion.comments.nodes; let latestCommentId: string | number; + if (comments?.length) { latestCommentId = getLatestDiscussionCommentId(comments); url += `#discussioncomment-${latestCommentId}`; @@ -146,18 +147,20 @@ export async function fetchDiscussion( const response: GraphQLSearch = await apiRequestAuth(`https://api.github.com/graphql`, 'POST', token, { query: `query fetchDiscussions( + $includeComments: Boolean!, $queryStatement: String!, $type: SearchType!, $firstDiscussions: Int, $lastComments: Int, - $includeComments: Boolean, - $lastReplies: Int + $firstReplies: Int ) { search(query:$queryStatement, type: $type, first: $firstDiscussions) { nodes { ... on Discussion { viewerSubscription title + stateReason + isAnswered url comments(last: $lastComments) @include(if: $includeComments){ nodes { @@ -207,13 +210,14 @@ export async function fetchDiscussion( return null; } -export const getLatestDiscussionCommentId = ( +export function getLatestDiscussionCommentId( comments: DiscussionCommentNode[], -) => - comments +) { + return comments .flatMap((comment) => comment.replies.nodes) .concat([comments.at(-1)]) .reduce((a, b) => (a.createdAt > b.createdAt ? a : b))?.databaseId; +} export async function generateGitHubWebUrl( notification: Notification, From 040c9df6a13444e717b8dd87090e4843b21c7af8 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Tue, 12 Mar 2024 06:48:04 -0400 Subject: [PATCH 5/5] refactor: update tests and logic --- src/hooks/useNotifications.test.ts | 12 +++++------- src/utils/helpers.test.ts | 2 +- src/utils/helpers.ts | 10 +++------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 6e245c9c8..4b020c8f3 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -256,14 +256,12 @@ describe('hooks/useNotifications.ts', () => { .reply(200, { data: { search: { - edges: [ + nodes: [ { - node: { - title: 'This is an answered discussion', - viewerSubscription: 'SUBSCRIBED', - stateReason: null, - isAnswered: true, - }, + title: 'This is an answered discussion', + viewerSubscription: 'SUBSCRIBED', + stateReason: null, + isAnswered: true, }, ], }, diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index ce5cd5198..d61e0006c 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -398,7 +398,7 @@ describe('utils/helpers.ts', () => { const requestPromise = new Promise((resolve) => resolve({ - data: {}, + data: { data: { search: { nodes: [] } } }, } as AxiosResponse), ) as AxiosPromise; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 4dfae02bc..62a72a746 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -148,7 +148,7 @@ export async function fetchDiscussion( notification: Notification, token: string, includeComments: boolean, -): Promise { +): Promise { const response: GraphQLSearch = await apiRequestAuth(`https://api.github.com/graphql`, 'POST', token, { query: `query fetchDiscussions( @@ -199,7 +199,7 @@ export async function fetchDiscussion( }); let discussions = - response?.data?.data?.search?.nodes?.filter( + response?.data?.data.search.nodes.filter( (discussion) => discussion.title === notification.subject.title, ) || []; @@ -208,11 +208,7 @@ export async function fetchDiscussion( (discussion) => discussion.viewerSubscription === 'SUBSCRIBED', ); - if (discussions[0]) { - return discussions[0]; - } - - return null; + return discussions[0]; } export function getLatestDiscussionCommentId(