From 1f0f975b0a5e396058552597e8c0d799dec97b56 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 30 May 2024 22:39:42 -0400 Subject: [PATCH 1/3] feat: linked issue indicator --- src/components/NotificationRow.test.tsx | 36 + src/components/NotificationRow.tsx | 14 + .../NotificationRow.test.tsx.snap | 1042 +++++++++++++++++ src/typesGitHub.ts | 1 + src/utils/subject.test.ts | 20 + src/utils/subject.ts | 22 + 6 files changed, 1135 insertions(+) diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index 7a2a05484..af7b1627d 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -117,6 +117,42 @@ describe('components/NotificationRow.tsx', () => { }); }); + describe('linked issues/prs', () => { + it('should render when linked to one issue/pr', async () => { + jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('2024').valueOf()); + + const mockNotification = mockSingleNotification; + mockNotification.subject.linkedIssues = ['#1']; + + const props = { + notification: mockNotification, + hostname: 'github.com', + }; + + const tree = render(); + expect(tree).toMatchSnapshot(); + }); + + it('should render when linked to multiple issues/prs', async () => { + jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('2024').valueOf()); + + const mockNotification = mockSingleNotification; + mockNotification.subject.linkedIssues = ['#1', '#2']; + + const props = { + notification: mockNotification, + hostname: 'github.com', + }; + + const tree = render(); + expect(tree).toMatchSnapshot(); + }); + }); + describe('notification interactions', () => { it('should open a notification in the browser - click', () => { const removeNotificationFromState = jest.fn(); diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index b322f0fe2..da257ed78 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -3,6 +3,7 @@ import { CheckIcon, CommentIcon, FeedPersonIcon, + IssueClosedIcon, ReadIcon, } from '@primer/octicons-react'; import { @@ -90,6 +91,10 @@ export const NotificationRow: FC = ({ notification, hostname }) => { notification.subject.comments > 1 ? 'comments' : 'comment' }`; + const linkedIssuesLabel = `Linked to ${ + notification.subject.linkedIssues?.length > 1 ? 'issues' : 'issue' + } ${notification.subject?.linkedIssues?.join(', ')}`; + return (
= ({ notification, hostname }) => { {reason.title} {updatedAt} + {notification.subject?.linkedIssues?.length > 0 && ( + + + + )} {notification.subject.reviews ? notification.subject.reviews.map((review) => { const icon = getPullRequestReviewIcon(review); diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap index 6b24aaae0..b8edbfc25 100644 --- a/src/components/__snapshots__/NotificationRow.test.tsx.snap +++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap @@ -1,5 +1,1047 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`components/NotificationRow.tsx linked issues/prs should render when linked to multiple issues/prs 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ + + + +
+
+
+ I am a robot and this is a test! +
+
+ + + + + + + Updated + + + over 6 years ago + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ , + "container":
+
+
+ + + + +
+
+
+ I am a robot and this is a test! +
+
+ + + + + + + Updated + + + over 6 years ago + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+
+
, + "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[`components/NotificationRow.tsx linked issues/prs should render when linked to one issue/pr 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ + + + +
+
+
+ I am a robot and this is a test! +
+
+ + + + + + + Updated + + + over 6 years ago + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ , + "container":
+
+
+ + + + +
+
+
+ I am a robot and this is a test! +
+
+ + + + + + + Updated + + + over 6 years ago + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+
+
, + "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[`components/NotificationRow.tsx rendering for notification comments count should render when 1 comment 1`] = ` { "asFragment": [Function], diff --git a/src/typesGitHub.ts b/src/typesGitHub.ts index ea86008b8..188504513 100644 --- a/src/typesGitHub.ts +++ b/src/typesGitHub.ts @@ -257,6 +257,7 @@ export interface GitifySubject { state?: StateType; user?: SubjectUser; reviews?: GitifyPullRequestReview[]; + linkedIssues?: string[]; comments?: number; } diff --git a/src/utils/subject.test.ts b/src/utils/subject.test.ts index df8930784..984105b61 100644 --- a/src/utils/subject.test.ts +++ b/src/utils/subject.test.ts @@ -18,6 +18,7 @@ import { getGitifySubjectDetails, getLatestReviewForReviewers, getWorkflowRunAttributes, + parseLinkedIssuesFromPrBody, } from './subject'; const mockAuthor = partialMockUser('some-author'); @@ -624,6 +625,7 @@ describe('utils/subject.ts', () => { type: mockCommenter.type, }, reviews: null, + linkedIssues: [], }); }); @@ -659,6 +661,7 @@ describe('utils/subject.ts', () => { type: mockCommenter.type, }, reviews: null, + linkedIssues: [], }); }); @@ -694,6 +697,7 @@ describe('utils/subject.ts', () => { type: mockCommenter.type, }, reviews: null, + linkedIssues: [], }); }); @@ -729,6 +733,7 @@ describe('utils/subject.ts', () => { type: mockCommenter.type, }, reviews: null, + linkedIssues: [], }); }); @@ -763,6 +768,7 @@ describe('utils/subject.ts', () => { type: mockAuthor.type, }, reviews: null, + linkedIssues: [], }); }); @@ -796,6 +802,7 @@ describe('utils/subject.ts', () => { type: mockAuthor.type, }, reviews: null, + linkedIssues: [], }); }); @@ -865,6 +872,19 @@ describe('utils/subject.ts', () => { expect(result).toBeNull(); }); }); + + describe('Pull Request Reviews - Extract Linked Issues', () => { + it('returns empty if no pr body', () => { + const result = parseLinkedIssuesFromPrBody(null); + expect(result).toEqual([]); + }); + + it('returns linked issues', () => { + const mockPrBody = 'This PR is linked to #1, #2, and #3'; + const result = parseLinkedIssuesFromPrBody(mockPrBody); + expect(result).toEqual(['#1', '#2', '#3']); + }); + }); }); describe('Releases', () => { diff --git a/src/utils/subject.ts b/src/utils/subject.ts index d899e7eac..cdf8c846e 100644 --- a/src/utils/subject.ts +++ b/src/utils/subject.ts @@ -260,6 +260,7 @@ async function getGitifySubjectForPullRequest( } const reviews = await getLatestReviewForReviewers(notification, token); + const linkedIssues = parseLinkedIssuesFromPrBody(pr.body); return { state: prState, @@ -271,6 +272,7 @@ async function getGitifySubjectForPullRequest( }, reviews: reviews, comments: pr.comments, + linkedIssues: linkedIssues, }; } @@ -326,6 +328,26 @@ export async function getLatestReviewForReviewers( }); } +export function parseLinkedIssuesFromPrBody(body: string): string[] { + const linkedIssues: string[] = []; + + if (!body) { + return linkedIssues; + } + + const regexPattern = /\s*#(\d+)\s*/gi; + + const matches = body.matchAll(regexPattern); + + for (const match of matches) { + if (match[0]) { + linkedIssues.push(match[0].trim()); + } + } + + return linkedIssues; +} + async function getGitifySubjectForRelease( notification: Notification, token: string, From 08c88ab6c2bf6e068d54f02af211adbe87423c91 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sun, 2 Jun 2024 15:29:35 -0500 Subject: [PATCH 2/3] format as pill --- src/components/NotificationRow.tsx | 18 +- .../NotificationRow.test.tsx.snap | 160 ++++++++++-------- src/utils/constants.ts | 3 + 3 files changed, 106 insertions(+), 75 deletions(-) diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index da257ed78..5143c06a0 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -18,6 +18,7 @@ import { AppContext } from '../context/App'; import { IconColor } from '../types'; import type { Notification } from '../typesGitHub'; import { openExternalLink } from '../utils/comms'; +import Constants from '../utils/constants'; import { formatForDisplay, formatNotificationUpdatedAt, @@ -91,7 +92,7 @@ export const NotificationRow: FC = ({ notification, hostname }) => { notification.subject.comments > 1 ? 'comments' : 'comment' }`; - const linkedIssuesLabel = `Linked to ${ + const linkedIssuesPillDescription = `Linked to ${ notification.subject.linkedIssues?.length > 1 ? 'issues' : 'issue' } ${notification.subject?.linkedIssues?.join(', ')}`; @@ -149,12 +150,15 @@ export const NotificationRow: FC = ({ notification, hostname }) => { {updatedAt} {notification.subject?.linkedIssues?.length > 0 && ( - - + + )} {notification.subject.reviews diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap index b8edbfc25..acdbb1030 100644 --- a/src/components/__snapshots__/NotificationRow.test.tsx.snap +++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap @@ -83,24 +83,30 @@ exports[`components/NotificationRow.tsx linked issues/prs should render when lin class="ml-1" title="Linked to issues #1, #2" > - - - - + + + + + 2 + - - - - + + + + + 2 + - - - - + + + + + 1 + - - - - + + + + + 1 + Date: Sun, 2 Jun 2024 15:36:25 -0500 Subject: [PATCH 3/3] format as pill --- src/components/NotificationRow.tsx | 2 +- .../__snapshots__/NotificationRow.test.tsx.snap | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index 5143c06a0..8c2fdb696 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -153,7 +153,7 @@ export const NotificationRow: FC = ({ notification, hostname }) => {