Repository: gitify-app/gitify
Pull request #928 (2024-03-27 09:42:28)

feat: add link to notification user profile
Add link to user profile. 

![Screenshot 2024-03-27 at 7 10 53 AM](https://github.com/gitify-app/gitify/assets/386277/bcba99f2-98cc-40b2-b138-7829c82548dc)




Patch:
diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts
index 7e05086f..a5820f61 100644
--- a/src/__mocks__/mockedData.ts
+++ b/src/__mocks__/mockedData.ts
@@ -299,6 +299,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-20T18:33:39Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -310,6 +311,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T03:30:42Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -321,6 +323,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T18:26:27Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -330,6 +333,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-02-23T00:57:58Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -341,6 +345,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-23T00:57:49Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -352,6 +357,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-27T01:22:20Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -361,6 +367,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:43:52Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -372,6 +379,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-04T20:39:44Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -381,6 +389,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:04Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -392,6 +401,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-05T11:05:42Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -401,6 +411,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:44Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -423,6 +434,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-20T18:33:39Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -434,6 +446,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T03:30:42Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -445,6 +458,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T18:26:27Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -454,6 +468,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-02-23T00:57:58Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -465,6 +480,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-23T00:57:49Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -476,6 +492,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-27T01:22:20Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -485,6 +502,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:43:52Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -496,6 +514,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-04T20:39:44Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -505,6 +524,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:04Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
@@ -516,6 +536,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-05T11:05:42Z',
                   author: {
                     login: 'comment-user',
+                    url: 'https://github.com/comment-user',
                     type: 'User',
                   },
                   replies: {
@@ -525,6 +546,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:44Z',
                         author: {
                           login: 'reply-user',
+                          url: 'https://github.com/reply-user',
                           type: 'User',
                         },
                       },
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 9cf2664a..d26a31d3 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -10,6 +10,7 @@ import {
 import { formatForDisplay, openInBrowser } from '../utils/helpers';
 import { Notification } from '../typesGithub';
 import { AppContext } from '../context/App';
+import { openExternalLink } from '../utils/comms';
 
 interface IProps {
   hostname: string;
@@ -79,13 +80,11 @@ export const NotificationRow: React.FC<IProps> = ({
         <NotificationIcon size={18} aria-label={notification.subject.type} />
       </div>
 
-      <div
-        className="flex-1 overflow-hidden cursor-pointer"
-        onClick={() => pressTitle()}
-        role="main"
-      >
+      <div className="flex-1 overflow-hidden">
         <div
-          className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden"
+          className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
+          role="main"
+          onClick={() => pressTitle()}
           title={notification.subject.title}
         >
           {notification.subject.title}
@@ -93,7 +92,24 @@ export const NotificationRow: React.FC<IProps> = ({
 
         <div className="text-xs text-capitalize whitespace-nowrap overflow-ellipsis overflow-hidden">
           <span title={reason.description}>{reason.type}</span> -{' '}
-          <span title={updatedLabel}>{updatedLabel}</span>
+          <span title={updatedLabel}>
+            Updated {updatedAt}
+            {notification.subject.user && (
+              <>
+                {' '}
+                by{' '}
+                <span
+                  className="cursor-pointer"
+                  title="View User Profile"
+                  onClick={() =>
+                    openExternalLink(notification.subject.user.html_url)
+                  }
+                >
+                  {notification.subject.user.login}
+                </span>
+              </>
+            )}
+          </span>
         </div>
       </div>
 
diff --git a/src/typesGithub.ts b/src/typesGithub.ts
index 01afa62c..5cb5543a 100644
--- a/src/typesGithub.ts
+++ b/src/typesGithub.ts
@@ -137,11 +137,13 @@ export interface User {
 
 export interface SubjectUser {
   login: string;
+  html_url: string;
   type: string;
 }
 
 export interface DiscussionAuthor {
   login: string;
+  url: string;
   type: string;
 }
 
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index f7832428..b1399501 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -160,6 +160,7 @@ export async function fetchDiscussion(
           createdAt
           author {
             login
+            url
             type
           }
         }
diff --git a/src/utils/subject.ts b/src/utils/subject.ts
index 8fb565b5..0d500afc 100644
--- a/src/utils/subject.ts
+++ b/src/utils/subject.ts
@@ -115,13 +115,14 @@ async function getGitifySubjectForDiscussion(
   if (latestDiscussionComment) {
     discussionUser = {
       login: latestDiscussionComment.author.login,
+      html_url: latestDiscussionComment.author.url,
       type: latestDiscussionComment.author.type,
     };
   }
 
   return {
     state: discussionState,
-    user: discussionUser ?? null,
+    user: discussionUser,
   };
 }
 
@@ -139,6 +140,7 @@ async function getGitifySubjectForIssue(
     state: issue.state_reason ?? issue.state,
     user: {
       login: issueCommentUser?.login ?? issue.user.login,
+      html_url: issueCommentUser?.html_url ?? issue.user.html_url,
       type: issueCommentUser?.type ?? issue.user.type,
     },
   };
@@ -165,6 +167,7 @@ async function getGitifySubjectForPullRequest(
     state: prState,
     user: {
       login: prCommentUser?.login ?? pr.user.login,
+      html_url: prCommentUser?.html_url ?? pr.user.html_url,
       type: prCommentUser?.type ?? pr.user.type,
     },
   };
@@ -180,6 +183,7 @@ async function getGitifySubjectForRelease(
     state: null,
     user: {
       login: releaseCommentUser.login,
+      html_url: releaseCommentUser.html_url,
       type: releaseCommentUser.type,
     },
   };


Test patch:
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index 9605e5d8..b9262362 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -8,6 +8,7 @@ import { AppContext } from '../context/App';
 import { mockedSingleNotification } from '../__mocks__/mockedData';
 import { NotificationRow } from './NotificationRow';
 import { mockAccounts, mockSettings } from '../__mocks__/mock-state';
+import { shell } from 'electron';
 
 describe('components/NotificationRow.tsx', () => {
   beforeEach(() => {
@@ -148,4 +149,35 @@ describe('components/NotificationRow.tsx', () => {
     fireEvent.click(getByTitle('Unsubscribe'));
     expect(unsubscribeNotification).toHaveBeenCalledTimes(1);
   });
+
+  it('should open notification user profile', () => {
+    const props = {
+      notification: {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          user: {
+            login: 'some-user',
+            html_url: 'https://github.com/some-user',
+            type: 'User',
+          },
+        },
+      },
+      hostname: 'github.com',
+    };
+
+    const { getByTitle } = render(
+      <AppContext.Provider
+        value={{
+          settings: { ...mockSettings },
+          accounts: mockAccounts,
+        }}
+      >
+        <NotificationRow {...props} />
+      </AppContext.Provider>,
+    );
+
+    fireEvent.click(getByTitle('View User Profile'));
+    expect(shell.openExternal).toHaveBeenCalledTimes(1);
+  });
 });
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index 52d225d9..219fe1bb 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -35,12 +35,12 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
     </svg>
   </div>
   <div
-    className="flex-1 overflow-hidden cursor-pointer"
-    onClick={[Function]}
-    role="main"
+    className="flex-1 overflow-hidden"
   >
     <div
-      className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden"
+      className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
+      onClick={[Function]}
+      role="main"
       title="I am a robot and this is a test!"
     >
       I am a robot and this is a test!
@@ -58,7 +58,8 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
       <span
         title="Updated in over 3 years"
       >
-        Updated in over 3 years
+        Updated 
+        in over 3 years
       </span>
     </div>
   </div>
diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts
index 549861c7..4b19d4cc 100644
--- a/src/hooks/useNotifications.test.ts
+++ b/src/hooks/useNotifications.test.ts
@@ -308,12 +308,19 @@ describe('hooks/useNotifications.ts', () => {
             merged: true,
             user: {
               login: 'some-user',
+              html_url: 'https://github.com/some-user',
               type: 'User',
             },
           });
         nock('https://api.github.com')
           .get('/3/comments')
-          .reply(200, { user: { login: 'some-commenter', type: 'User' } });
+          .reply(200, {
+            user: {
+              login: 'some-commenter',
+              html_url: 'https://github.com/some-commenter',
+              type: 'User',
+            },
+          });
         nock('https://api.github.com')
           .get('/4')
           .reply(200, {
@@ -321,12 +328,19 @@ describe('hooks/useNotifications.ts', () => {
             merged: false,
             user: {
               login: 'some-user',
+              html_url: 'https://github.com/some-user',
               type: 'User',
             },
           });
         nock('https://api.github.com')
           .get('/4/comments')
-          .reply(200, { user: { login: 'some-commenter', type: 'User' } });
+          .reply(200, {
+            user: {
+              login: 'some-commenter',
+              html_url: 'https://github.com/some-commenter',
+              type: 'User',
+            },
+          });
 
         const { result } = renderHook(() => useNotifications(true));