diff --git a/src/renderer/components/metrics/MetricGroup.test.tsx b/src/renderer/components/metrics/MetricGroup.test.tsx index ade9d6c31..4e3f2cda8 100644 --- a/src/renderer/components/metrics/MetricGroup.test.tsx +++ b/src/renderer/components/metrics/MetricGroup.test.tsx @@ -48,6 +48,102 @@ describe('renderer/components/metrics/MetricGroup.tsx', () => { }); }); + describe('reactions pills', () => { + it('should render reaction pill when one reaction', async () => { + const mockNotification = mockGitifyNotification; + mockNotification.subject.reactionsCount = 1; + mockNotification.subject.reactionGroups = [ + { + content: 'ROCKET', + reactors: { + totalCount: 1, + }, + }, + ]; + + const props: MetricGroupProps = { + notification: mockNotification, + }; + + const tree = renderWithAppContext(); + expect(tree).toMatchSnapshot(); + }); + + it('should render reaction pill when multiple reactions', async () => { + const mockNotification = mockGitifyNotification; + mockNotification.subject.reactionsCount = 53; + mockNotification.subject.reactionGroups = [ + { + content: 'THUMBS_UP', + reactors: { + totalCount: 1, + }, + }, + { + content: 'THUMBS_DOWN', + reactors: { + totalCount: 1, + }, + }, + { + content: 'LAUGH', + reactors: { + totalCount: 2, + }, + }, + { + content: 'HOORAY', + reactors: { + totalCount: 3, + }, + }, + { + content: 'CONFUSED', + reactors: { + totalCount: 5, + }, + }, + { + content: 'ROCKET', + reactors: { + totalCount: 8, + }, + }, + { + content: 'EYES', + reactors: { + totalCount: 13, + }, + }, + { + content: 'HEART', + reactors: { + totalCount: 21, + }, + }, + ]; + + const props: MetricGroupProps = { + notification: mockNotification, + }; + + const tree = renderWithAppContext(); + expect(tree).toMatchSnapshot(); + }); + + it('should render issues pill when linked to multiple issues/prs', async () => { + const mockNotification = mockGitifyNotification; + mockNotification.subject.linkedIssues = ['#1', '#2']; + + const props: MetricGroupProps = { + notification: mockNotification, + }; + + const tree = renderWithAppContext(); + expect(tree).toMatchSnapshot(); + }); + }); + describe('comment pills', () => { it('should render when no comments', async () => { const mockNotification = mockGitifyNotification; diff --git a/src/renderer/components/metrics/MetricGroup.tsx b/src/renderer/components/metrics/MetricGroup.tsx index 2cdd800fd..07a150f45 100644 --- a/src/renderer/components/metrics/MetricGroup.tsx +++ b/src/renderer/components/metrics/MetricGroup.tsx @@ -4,6 +4,7 @@ import { CommentIcon, IssueOpenedIcon, MilestoneIcon, + SmileyIcon, TagIcon, } from '@primer/octicons-react'; @@ -12,6 +13,7 @@ import { useAppContext } from '../../hooks/useAppContext'; import { type GitifyNotification, IconColor } from '../../types'; import { getPullRequestReviewIcon } from '../../utils/icons'; +import { formatMetricDescription } from '../../utils/notifications/formatters'; import { MetricPill } from './MetricPill'; export interface MetricGroupProps { @@ -25,25 +27,73 @@ export const MetricGroup: FC = ({ const linkedIssues = notification.subject.linkedIssues ?? []; const hasLinkedIssues = linkedIssues.length > 0; - const linkedIssuesPillDescription = hasLinkedIssues - ? `Linked to ${ - linkedIssues.length > 1 ? 'issues' : 'issue' - } ${linkedIssues.join(', ')}` - : ''; + const linkedIssuesPillDescription = formatMetricDescription( + linkedIssues.length, + 'issue', + (count, noun) => { + return `Linked to ${count} ${noun}: ${linkedIssues.join(', ')}`; + }, + ); + + const reactionCount = notification.subject.reactionsCount ?? 0; + const reactionGroups = notification.subject.reactionGroups ?? []; + const hasReactions = reactionCount > 0; + const hasMultipleReactions = + reactionGroups.filter((rg) => rg.reactors.totalCount > 0).length > 1; + const reactionEmojiMap: Record = { + THUMBS_UP: '👍', + THUMBS_DOWN: '👎', + LAUGH: '😆', + HOORAY: '🎉', + CONFUSED: '😕', + ROCKET: '🚀', + EYES: '👀', + HEART: '❤️', + }; + + const reactionPillDescription = formatMetricDescription( + reactionCount, + 'reaction', + (count, noun) => { + const formatted = reactionGroups + .map((rg) => { + const emoji = reactionEmojiMap[rg.content]; + if (!emoji || !rg.reactors.totalCount) { + return ''; + } + + return `${emoji} ${hasMultipleReactions ? rg.reactors.totalCount : ''}`.trim(); + }) + .filter(Boolean) + .join(' '); + + return `${count} ${noun}: ${formatted}`; + }, + ); const commentCount = notification.subject.commentCount ?? 0; const hasComments = commentCount > 0; - const commentsPillDescription = hasComments - ? `${notification.subject.commentCount} ${ - notification.subject.commentCount > 1 ? 'comments' : 'comment' - }` - : ''; + const commentsPillDescription = formatMetricDescription( + commentCount, + 'comment', + ); const labels = notification.subject.labels ?? []; - const hasLabels = labels.length > 0; - const labelsPillDescription = hasLabels - ? labels.map((label) => `🏷️ ${label}`).join(', ') - : ''; + const labelsCount = labels.length; + const hasLabels = labelsCount > 0; + const labelsPillDescription = formatMetricDescription( + labelsCount, + 'label', + (count, noun) => { + const formatted = labels + .map((label) => { + return `🏷️ ${label}`.trim(); + }) + .join(', '); + + return `${count} ${noun}: ${formatted}`; + }, + ); const milestone = notification.subject.milestone; @@ -59,6 +109,15 @@ export const MetricGroup: FC = ({ /> )} + {hasReactions && ( + + )} + {notification.subject.reviews?.map((review) => { const icon = getPullRequestReviewIcon(review); if (!icon) { diff --git a/src/renderer/components/metrics/__snapshots__/MetricGroup.test.tsx.snap b/src/renderer/components/metrics/__snapshots__/MetricGroup.test.tsx.snap index c632a7e5b..0766a3935 100644 --- a/src/renderer/components/metrics/__snapshots__/MetricGroup.test.tsx.snap +++ b/src/renderer/components/metrics/__snapshots__/MetricGroup.test.tsx.snap @@ -71,7 +71,60 @@ exports[`renderer/components/metrics/MetricGroup.tsx comment pills should render popover="auto" role="tooltip" > - Linked to issues #1, #2 + Linked to 2 issues: #1, #2 + + + + + + + + + , @@ -1534,7 +1852,60 @@ exports[`renderer/components/metrics/MetricGroup.tsx label pills should render l popover="auto" role="tooltip" > - Linked to issues #1, #2 + Linked to 2 issues: #1, #2 + + + + + + + + + + + + + + + + + + + + + , + "container":
+
+
+
+ + + + + + + + +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`renderer/components/metrics/MetricGroup.tsx reactions pills should render reaction pill when multiple reactions 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+ + + + + + + + +
+
+
+
+ , + "container":
+
+
+
+ + + + + + + + +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`renderer/components/metrics/MetricGroup.tsx reactions pills should render reaction pill when one reaction 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+ + + + + + + + +
+
+
+
+ , + "container":
+
+
+
+ + + + + + + + +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + exports[`renderer/components/metrics/MetricGroup.tsx showPills disabled should not render any pills when showPills is disabled 1`] = ` { "asFragment": [Function], diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 0255f55fa..9a2c8663b 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -11,6 +11,7 @@ import type { MilestoneFieldsFragment, PullRequestReviewState, PullRequestState, + ReactionGroupFieldsFragment, } from './utils/api/graphql/generated/graphql'; declare const __brand: unique symbol; @@ -338,6 +339,10 @@ export interface GitifySubject { milestone?: GitifyMilestone; /** Deep link to notification thread */ htmlUrl?: Link; + /** Reaction counts */ + reactionsCount?: number; + /** Reaction groups */ + reactionGroups?: GitifyReactionGroup[]; } /** @@ -401,6 +406,8 @@ export interface GitifyNotificationDisplay { export type GitifyMilestone = MilestoneFieldsFragment; +export type GitifyReactionGroup = ReactionGroupFieldsFragment; + export interface GitifyPullRequestReview { state: PullRequestReviewState; users: string[]; diff --git a/src/renderer/utils/api/__mocks__/response-mocks.ts b/src/renderer/utils/api/__mocks__/response-mocks.ts index e81dc9c1c..983d89c24 100644 --- a/src/renderer/utils/api/__mocks__/response-mocks.ts +++ b/src/renderer/utils/api/__mocks__/response-mocks.ts @@ -10,6 +10,7 @@ import type { IssueStateReason, PullRequestDetailsFragment, PullRequestState, + ReactionGroupFieldsFragment, } from '../graphql/generated/graphql'; /** @@ -101,6 +102,10 @@ export function mockIssueResponseNode(mocks: { labels: { nodes: [] }, comments: { totalCount: 0, nodes: [] }, milestone: null, + reactions: { + totalCount: 0, + }, + reactionGroups: noReactionGroups, } satisfies IssueDetailsFragment; } @@ -136,5 +141,60 @@ export function mockPullRequestResponseNode(mocks: { closingIssuesReferences: { nodes: [], }, + reactions: { + totalCount: 0, + }, + reactionGroups: noReactionGroups, } satisfies PullRequestDetailsFragment; } + +export const noReactionGroups = [ + { + content: 'THUMBS_UP', + reactors: { + totalCount: 0, + }, + }, + { + content: 'THUMBS_DOWN', + reactors: { + totalCount: 0, + }, + }, + { + content: 'LAUGH', + reactors: { + totalCount: 0, + }, + }, + { + content: 'HOORAY', + reactors: { + totalCount: 0, + }, + }, + { + content: 'CONFUSED', + reactors: { + totalCount: 0, + }, + }, + { + content: 'HEART', + reactors: { + totalCount: 0, + }, + }, + { + content: 'ROCKET', + reactors: { + totalCount: 0, + }, + }, + { + content: 'EYES', + reactors: { + totalCount: 0, + }, + }, +] as ReactionGroupFieldsFragment[]; diff --git a/src/renderer/utils/api/graphql/common.graphql b/src/renderer/utils/api/graphql/common.graphql index b1e5a8aef..1ebdda615 100644 --- a/src/renderer/utils/api/graphql/common.graphql +++ b/src/renderer/utils/api/graphql/common.graphql @@ -9,3 +9,10 @@ fragment MilestoneFields on Milestone { state title } + +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} diff --git a/src/renderer/utils/api/graphql/generated/gql.ts b/src/renderer/utils/api/graphql/generated/gql.ts index 8eae3c279..246dcfaef 100644 --- a/src/renderer/utils/api/graphql/generated/gql.ts +++ b/src/renderer/utils/api/graphql/generated/gql.ts @@ -15,24 +15,24 @@ import * as types from './graphql'; * Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size */ type Documents = { - "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}": typeof types.AuthorFieldsFragmentDoc, + "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}\n\nfragment ReactionGroupFields on ReactionGroup {\n content\n reactors {\n totalCount\n }\n}": typeof types.AuthorFieldsFragmentDoc, "query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastThreadedComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n repository(owner: $owner, name: $name) {\n discussion(number: $number) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastThreadedComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": typeof types.FetchDiscussionByNumberDocument, - "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": typeof types.FetchIssueByNumberDocument, + "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}": typeof types.FetchIssueByNumberDocument, "query FetchMergedDetailsTemplate($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $isDiscussionNotificationINDEX: Boolean!, $isIssueNotificationINDEX: Boolean!, $isPullRequestNotificationINDEX: Boolean!, $lastComments: Int, $lastThreadedComments: Int, $lastReplies: Int, $lastReviews: Int, $firstLabels: Int, $firstClosingIssues: Int, $includeIsAnswered: Boolean!) {\n ...MergedDetailsQueryTemplate\n}\n\nfragment MergedDetailsQueryTemplate on Query {\n repository(owner: $ownerINDEX, name: $nameINDEX) {\n discussion(number: $numberINDEX) @include(if: $isDiscussionNotificationINDEX) {\n ...DiscussionDetails\n }\n issue(number: $numberINDEX) @include(if: $isIssueNotificationINDEX) {\n ...IssueDetails\n }\n pullRequest(number: $numberINDEX) @include(if: $isPullRequestNotificationINDEX) {\n ...PullRequestDetails\n }\n }\n}": typeof types.FetchMergedDetailsTemplateDocument, - "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": typeof types.FetchPullRequestByNumberDocument, + "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": typeof types.FetchPullRequestByNumberDocument, }; const documents: Documents = { - "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}": types.AuthorFieldsFragmentDoc, + "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}\n\nfragment ReactionGroupFields on ReactionGroup {\n content\n reactors {\n totalCount\n }\n}": types.AuthorFieldsFragmentDoc, "query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastThreadedComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n repository(owner: $owner, name: $name) {\n discussion(number: $number) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastThreadedComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": types.FetchDiscussionByNumberDocument, - "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": types.FetchIssueByNumberDocument, + "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}": types.FetchIssueByNumberDocument, "query FetchMergedDetailsTemplate($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $isDiscussionNotificationINDEX: Boolean!, $isIssueNotificationINDEX: Boolean!, $isPullRequestNotificationINDEX: Boolean!, $lastComments: Int, $lastThreadedComments: Int, $lastReplies: Int, $lastReviews: Int, $firstLabels: Int, $firstClosingIssues: Int, $includeIsAnswered: Boolean!) {\n ...MergedDetailsQueryTemplate\n}\n\nfragment MergedDetailsQueryTemplate on Query {\n repository(owner: $ownerINDEX, name: $nameINDEX) {\n discussion(number: $numberINDEX) @include(if: $isDiscussionNotificationINDEX) {\n ...DiscussionDetails\n }\n issue(number: $numberINDEX) @include(if: $isIssueNotificationINDEX) {\n ...IssueDetails\n }\n pullRequest(number: $numberINDEX) @include(if: $isPullRequestNotificationINDEX) {\n ...PullRequestDetails\n }\n }\n}": types.FetchMergedDetailsTemplateDocument, - "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": types.FetchPullRequestByNumberDocument, + "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": types.FetchPullRequestByNumberDocument, }; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}"): typeof import('./graphql').AuthorFieldsFragmentDoc; +export function graphql(source: "fragment AuthorFields on Actor {\n login\n htmlUrl: url\n avatarUrl: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}\n\nfragment ReactionGroupFields on ReactionGroup {\n content\n reactors {\n totalCount\n }\n}"): typeof import('./graphql').AuthorFieldsFragmentDoc; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -40,7 +40,7 @@ export function graphql(source: "query FetchDiscussionByNumber($owner: String!, /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}"): typeof import('./graphql').FetchIssueByNumberDocument; +export function graphql(source: "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}"): typeof import('./graphql').FetchIssueByNumberDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -48,7 +48,7 @@ export function graphql(source: "query FetchMergedDetailsTemplate($ownerINDEX: S /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}"): typeof import('./graphql').FetchPullRequestByNumberDocument; +export function graphql(source: "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n reactions {\n totalCount\n }\n reactionGroups {\n ...ReactionGroupFields\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}"): typeof import('./graphql').FetchPullRequestByNumberDocument; export function graphql(source: string) { diff --git a/src/renderer/utils/api/graphql/generated/graphql.ts b/src/renderer/utils/api/graphql/generated/graphql.ts index f363f5a5c..7ae278df9 100644 --- a/src/renderer/utils/api/graphql/generated/graphql.ts +++ b/src/renderer/utils/api/graphql/generated/graphql.ts @@ -9416,7 +9416,7 @@ export type Issue = Assignable & Closable & Comment & Deletable & Labelable & Lo */ projectCards: ProjectCardConnection; /** List of project items associated with this issue. */ - projectItems: ProjectV2ItemConnection; + projectItems?: Maybe; /** Find a project by number. */ projectV2?: Maybe; /** A list of projects under the owner. */ @@ -21362,7 +21362,7 @@ export type PullRequest = Assignable & Closable & Comment & Labelable & Lockable */ projectCards: ProjectCardConnection; /** List of project items associated with this pull request. */ - projectItems: ProjectV2ItemConnection; + projectItems?: Maybe; /** Find a project by number. */ projectV2?: Maybe; /** A list of projects under the owner. */ @@ -35606,6 +35606,8 @@ export type AuthorFieldsFragment = export type MilestoneFieldsFragment = { __typename?: 'Milestone', state: MilestoneState, title: string }; +export type ReactionGroupFieldsFragment = { __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }; + export type FetchDiscussionByNumberQueryVariables = Exact<{ owner: Scalars['String']['input']; name: Scalars['String']['input']; @@ -35700,7 +35702,7 @@ export type FetchIssueByNumberQuery = { __typename?: 'Query', repository?: { __t | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null } | null } | null }; + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null } | null }; export type IssueDetailsFragment = { __typename: 'Issue', number: number, title: string, url: any, state: IssueState, stateReason?: IssueStateReason | null, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, htmlUrl: any, avatarUrl: any, type: 'Bot' } @@ -35714,7 +35716,7 @@ export type IssueDetailsFragment = { __typename: 'Issue', number: number, title: | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null }; + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null }; export type FetchMergedDetailsTemplateQueryVariables = Exact<{ ownerINDEX: Scalars['String']['input']; @@ -35763,7 +35765,7 @@ export type FetchMergedDetailsTemplateQuery = { __typename?: 'Query', repository | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null } | null, pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null, pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, htmlUrl: any, avatarUrl: any, type: 'Bot' } | { __typename?: 'EnterpriseUserAccount', login: string, htmlUrl: any, avatarUrl: any, type: 'EnterpriseUserAccount' } | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } @@ -35775,13 +35777,13 @@ export type FetchMergedDetailsTemplateQuery = { __typename?: 'Query', repository | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: | { __typename?: 'Bot', login: string } | { __typename?: 'EnterpriseUserAccount', login: string } | { __typename?: 'Mannequin', login: string } | { __typename?: 'Organization', login: string } | { __typename?: 'User', login: string } - | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null } | null } | null }; + | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null } | null }; export type MergedDetailsQueryTemplateFragment = { __typename?: 'Query', repository?: { __typename?: 'Repository', discussion?: { __typename: 'Discussion', number: number, title: string, stateReason?: DiscussionStateReason | null, isAnswered?: boolean | null, url: any, author?: | { __typename?: 'Bot', login: string, htmlUrl: any, avatarUrl: any, type: 'Bot' } @@ -35813,7 +35815,7 @@ export type MergedDetailsQueryTemplateFragment = { __typename?: 'Query', reposit | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null } | null, pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null, pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, htmlUrl: any, avatarUrl: any, type: 'Bot' } | { __typename?: 'EnterpriseUserAccount', login: string, htmlUrl: any, avatarUrl: any, type: 'EnterpriseUserAccount' } | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } @@ -35825,13 +35827,13 @@ export type MergedDetailsQueryTemplateFragment = { __typename?: 'Query', reposit | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: | { __typename?: 'Bot', login: string } | { __typename?: 'EnterpriseUserAccount', login: string } | { __typename?: 'Mannequin', login: string } | { __typename?: 'Organization', login: string } | { __typename?: 'User', login: string } - | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null } | null } | null }; + | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null } | null }; export type FetchPullRequestByNumberQueryVariables = Exact<{ owner: Scalars['String']['input']; @@ -35856,13 +35858,13 @@ export type FetchPullRequestByNumberQuery = { __typename?: 'Query', repository?: | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: | { __typename?: 'Bot', login: string } | { __typename?: 'EnterpriseUserAccount', login: string } | { __typename?: 'Mannequin', login: string } | { __typename?: 'Organization', login: string } | { __typename?: 'User', login: string } - | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null } | null } | null }; + | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null } | null }; export type PullRequestDetailsFragment = { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, htmlUrl: any, avatarUrl: any, type: 'Bot' } @@ -35876,13 +35878,13 @@ export type PullRequestDetailsFragment = { __typename: 'PullRequest', number: nu | { __typename?: 'Mannequin', login: string, htmlUrl: any, avatarUrl: any, type: 'Mannequin' } | { __typename?: 'Organization', login: string, htmlUrl: any, avatarUrl: any, type: 'Organization' } | { __typename?: 'User', login: string, htmlUrl: any, avatarUrl: any, type: 'User' } - | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: + | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: | { __typename?: 'Bot', login: string } | { __typename?: 'EnterpriseUserAccount', login: string } | { __typename?: 'Mannequin', login: string } | { __typename?: 'Organization', login: string } | { __typename?: 'User', login: string } - | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null }; + | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null, reactions: { __typename?: 'ReactionConnection', totalCount: number }, reactionGroups?: Array<{ __typename?: 'ReactionGroup', content: ReactionContent, reactors: { __typename?: 'ReactorConnection', totalCount: number } }> | null }; export type PullRequestReviewFieldsFragment = { __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: | { __typename?: 'Bot', login: string } @@ -36009,6 +36011,14 @@ export const MilestoneFieldsFragmentDoc = new TypedDocumentString(` title } `, {"fragmentName":"MilestoneFields"}) as unknown as TypedDocumentString; +export const ReactionGroupFieldsFragmentDoc = new TypedDocumentString(` + fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} + `, {"fragmentName":"ReactionGroupFields"}) as unknown as TypedDocumentString; export const IssueDetailsFragmentDoc = new TypedDocumentString(` fragment IssueDetails on Issue { __typename @@ -36030,6 +36040,12 @@ export const IssueDetailsFragmentDoc = new TypedDocumentString(` author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } labels(first: $firstLabels) { @@ -36037,6 +36053,12 @@ export const IssueDetailsFragmentDoc = new TypedDocumentString(` name } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment AuthorFields on Actor { login @@ -36047,6 +36069,12 @@ export const IssueDetailsFragmentDoc = new TypedDocumentString(` fragment MilestoneFields on Milestone { state title +} +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } }`, {"fragmentName":"IssueDetails"}) as unknown as TypedDocumentString; export const PullRequestReviewFieldsFragmentDoc = new TypedDocumentString(` fragment PullRequestReviewFields on PullRequestReview { @@ -36079,6 +36107,12 @@ export const PullRequestDetailsFragmentDoc = new TypedDocumentString(` author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } reviews(last: $lastReviews) { @@ -36097,6 +36131,12 @@ export const PullRequestDetailsFragmentDoc = new TypedDocumentString(` number } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment AuthorFields on Actor { login @@ -36108,6 +36148,12 @@ fragment MilestoneFields on Milestone { state title } +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} fragment PullRequestReviewFields on PullRequestReview { state author { @@ -36138,6 +36184,12 @@ fragment MilestoneFields on Milestone { state title } +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} fragment DiscussionDetails on Discussion { __typename number @@ -36197,6 +36249,12 @@ fragment IssueDetails on Issue { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } labels(first: $firstLabels) { @@ -36204,6 +36262,12 @@ fragment IssueDetails on Issue { name } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment PullRequestDetails on PullRequest { __typename @@ -36227,6 +36291,12 @@ fragment PullRequestDetails on PullRequest { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } reviews(last: $lastReviews) { @@ -36245,6 +36315,12 @@ fragment PullRequestDetails on PullRequest { number } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment PullRequestReviewFields on PullRequestReview { state @@ -36323,6 +36399,12 @@ fragment MilestoneFields on Milestone { state title } +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} fragment IssueDetails on Issue { __typename number @@ -36343,6 +36425,12 @@ fragment IssueDetails on Issue { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } labels(first: $firstLabels) { @@ -36350,6 +36438,12 @@ fragment IssueDetails on Issue { name } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } }`) as unknown as TypedDocumentString; export const FetchMergedDetailsTemplateDocument = new TypedDocumentString(` query FetchMergedDetailsTemplate($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $isDiscussionNotificationINDEX: Boolean!, $isIssueNotificationINDEX: Boolean!, $isPullRequestNotificationINDEX: Boolean!, $lastComments: Int, $lastThreadedComments: Int, $lastReplies: Int, $lastReviews: Int, $firstLabels: Int, $firstClosingIssues: Int, $includeIsAnswered: Boolean!) { @@ -36365,6 +36459,12 @@ fragment MilestoneFields on Milestone { state title } +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} fragment DiscussionDetails on Discussion { __typename number @@ -36424,6 +36524,12 @@ fragment IssueDetails on Issue { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } labels(first: $firstLabels) { @@ -36431,6 +36537,12 @@ fragment IssueDetails on Issue { name } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment MergedDetailsQueryTemplate on Query { repository(owner: $ownerINDEX, name: $nameINDEX) { @@ -36467,6 +36579,12 @@ fragment PullRequestDetails on PullRequest { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } reviews(last: $lastReviews) { @@ -36485,6 +36603,12 @@ fragment PullRequestDetails on PullRequest { number } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment PullRequestReviewFields on PullRequestReview { state @@ -36510,6 +36634,12 @@ fragment MilestoneFields on Milestone { state title } +fragment ReactionGroupFields on ReactionGroup { + content + reactors { + totalCount + } +} fragment PullRequestDetails on PullRequest { __typename number @@ -36532,6 +36662,12 @@ fragment PullRequestDetails on PullRequest { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } reviews(last: $lastReviews) { @@ -36550,6 +36686,12 @@ fragment PullRequestDetails on PullRequest { number } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment PullRequestReviewFields on PullRequestReview { state diff --git a/src/renderer/utils/api/graphql/issue.graphql b/src/renderer/utils/api/graphql/issue.graphql index ceb1b77e0..4c36aac4b 100644 --- a/src/renderer/utils/api/graphql/issue.graphql +++ b/src/renderer/utils/api/graphql/issue.graphql @@ -34,6 +34,12 @@ fragment IssueDetails on Issue { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } labels(first: $firstLabels) { @@ -41,4 +47,10 @@ fragment IssueDetails on Issue { name } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } diff --git a/src/renderer/utils/api/graphql/pull.graphql b/src/renderer/utils/api/graphql/pull.graphql index 857580b39..89b86fd53 100644 --- a/src/renderer/utils/api/graphql/pull.graphql +++ b/src/renderer/utils/api/graphql/pull.graphql @@ -38,6 +38,12 @@ fragment PullRequestDetails on PullRequest { author { ...AuthorFields } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } } reviews(last: $lastReviews) { @@ -56,6 +62,12 @@ fragment PullRequestDetails on PullRequest { number } } + reactions { + totalCount + } + reactionGroups { + ...ReactionGroupFields + } } fragment PullRequestReviewFields on PullRequestReview { diff --git a/src/renderer/utils/api/graphql/utils.test.ts b/src/renderer/utils/api/graphql/utils.test.ts index 27a5ab7af..14ae99ff6 100644 --- a/src/renderer/utils/api/graphql/utils.test.ts +++ b/src/renderer/utils/api/graphql/utils.test.ts @@ -53,10 +53,11 @@ describe('renderer/utils/api/graphql/utils.ts', () => { ); expect(fragments).not.toBeNull(); - expect(fragments.length).toBe(8); + expect(fragments.length).toBe(9); expect(fragments.flatMap((f) => f.typeCondition)).toEqual([ 'Actor', 'Milestone', + 'ReactionGroup', 'Discussion', 'DiscussionComment', 'DiscussionComment', diff --git a/src/renderer/utils/notifications/formatters.test.ts b/src/renderer/utils/notifications/formatters.test.ts index 54c561ada..e945d991c 100644 --- a/src/renderer/utils/notifications/formatters.test.ts +++ b/src/renderer/utils/notifications/formatters.test.ts @@ -2,6 +2,7 @@ import { mockPartialGitifyNotification } from '../../__mocks__/notifications-moc import { formatForDisplay, + formatMetricDescription, formatNotificationNumber, formatNotificationTitle, formatNotificationType, @@ -87,4 +88,26 @@ describe('renderer/utils/notifications/formatters.ts', () => { expect(formatNotificationTitle(notification)).toBe('Improve docs'); }); }); + + describe('formatMetricDescription', () => { + it('return empty if no count', () => { + expect(formatMetricDescription(null, 'bee')).toBe(''); + }); + + it('return singular if count is 1', () => { + expect(formatMetricDescription(1, 'bee')).toBe('1 bee'); + }); + + it('return pluralized if count is more than 1', () => { + expect(formatMetricDescription(2, 'bee')).toBe('2 bees'); + }); + + it('return with custom formatter', () => { + expect( + formatMetricDescription(2, 'bee', (_count, noun) => { + return `Hi ${noun}`; + }), + ).toBe('Hi bees'); + }); + }); }); diff --git a/src/renderer/utils/notifications/formatters.ts b/src/renderer/utils/notifications/formatters.ts index 850325eea..1773b4d9f 100644 --- a/src/renderer/utils/notifications/formatters.ts +++ b/src/renderer/utils/notifications/formatters.ts @@ -91,3 +91,21 @@ export function formatNotificationTitle( return title; } + +/** + * Helper to format the metric description, determine singular or plural noun, + * and apply a custom formatter if needed. + */ +export function formatMetricDescription( + count: number, + singular: string, + formatter?: (count: number, noun: string) => string, +) { + if (!count) { + return ''; + } + + const noun = count === 1 ? singular : `${singular}s`; + + return formatter ? formatter(count, noun) : `${count} ${noun}`; +} diff --git a/src/renderer/utils/notifications/handlers/issue.test.ts b/src/renderer/utils/notifications/handlers/issue.test.ts index 331d857d3..b40c55de7 100644 --- a/src/renderer/utils/notifications/handlers/issue.test.ts +++ b/src/renderer/utils/notifications/handlers/issue.test.ts @@ -4,6 +4,7 @@ import { mockAuthor, mockCommenter, mockIssueResponseNode, + noReactionGroups, } from '../../api/__mocks__/response-mocks'; import type { GitifyNotification } from '../../../types'; @@ -63,6 +64,8 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { 'https://github.com/gitify-app/notifications-test/issues/123' as Link, labels: [], milestone: undefined, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -94,6 +97,8 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { 'https://github.com/gitify-app/notifications-test/issues/123' as Link, labels: [], milestone: undefined, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -107,6 +112,9 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { { author: mockCommenter, url: 'https://github.com/gitify-app/notifications-test/issues/123#issuecomment-1234', + reactions: { + totalCount: 0, + }, }, ], }; @@ -132,6 +140,8 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { 'https://github.com/gitify-app/notifications-test/issues/123#issuecomment-1234' as Link, labels: [], milestone: undefined, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -165,6 +175,8 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { 'https://github.com/gitify-app/notifications-test/issues/123' as Link, labels: ['enhancement'], milestone: undefined, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -202,6 +214,8 @@ describe('renderer/utils/notifications/handlers/issue.ts', () => { state: 'OPEN', title: 'Open Milestone', }, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); }); diff --git a/src/renderer/utils/notifications/handlers/issue.ts b/src/renderer/utils/notifications/handlers/issue.ts index b28dfaff7..7b50592c6 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -44,6 +44,11 @@ class IssueHandler extends DefaultHandler { issue.author, ]); + const issueReactionCount = + issueComment?.reactions.totalCount ?? issue.reactions.totalCount; + const issueReactionGroup = + issueComment?.reactionGroups ?? issue.reactionGroups; + return { number: issue.number, state: issueState, @@ -52,6 +57,8 @@ class IssueHandler extends DefaultHandler { labels: issue.labels?.nodes.map((label) => label.name) ?? [], milestone: issue.milestone ?? undefined, htmlUrl: issueComment?.url ?? issue.url, + reactionsCount: issueReactionCount, + reactionGroups: issueReactionGroup, }; } diff --git a/src/renderer/utils/notifications/handlers/pullRequest.test.ts b/src/renderer/utils/notifications/handlers/pullRequest.test.ts index 1a68272b6..8f0e26d42 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.test.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.test.ts @@ -4,6 +4,7 @@ import { mockAuthor, mockCommenter, mockPullRequestResponseNode, + noReactionGroups, } from '../../api/__mocks__/response-mocks'; import type { GitifyNotification } from '../../../types'; @@ -71,6 +72,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -107,6 +110,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -143,6 +148,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -179,6 +186,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -192,6 +201,9 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { { author: mockCommenter, url: 'https://github.com/gitify-app/notifications-test/pulls/123#issuecomment-1234', + reactions: { + totalCount: 0, + }, }, ], }; @@ -223,6 +235,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123#issuecomment-1234' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -265,6 +279,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -307,6 +323,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { milestone: null, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); @@ -349,6 +367,8 @@ describe('renderer/utils/notifications/handlers/pullRequest.ts', () => { }, htmlUrl: 'https://github.com/gitify-app/notifications-test/pulls/123' as Link, + reactionsCount: 0, + reactionGroups: noReactionGroups, } satisfies Partial); }); }); diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index 7d9d10f30..d8c8b5400 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -55,6 +55,10 @@ class PullRequestHandler extends DefaultHandler { const reviews = getLatestReviewForReviewers(pr.reviews.nodes); + const prReactionCount = + prComment?.reactions.totalCount ?? pr.reactions.totalCount; + const prReactionGroup = prComment?.reactionGroups ?? pr.reactionGroups; + return { number: pr.number, state: prState, @@ -67,6 +71,8 @@ class PullRequestHandler extends DefaultHandler { ), milestone: pr.milestone, htmlUrl: prComment?.url ?? pr.url, + reactionsCount: prReactionCount, + reactionGroups: prReactionGroup, }; }