Repository: gitify-app/gitify
Pull request #1184 (2024-06-05 16:45:29)

feat: add pill display settings
Add display setting to toggle showing metric pills


Patch:
diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts
index 118e11bb..13dc0852 100644
--- a/src/__mocks__/state-mocks.ts
+++ b/src/__mocks__/state-mocks.ts
@@ -71,4 +71,5 @@ export const mockSettings: SettingsState = {
   markAsDoneOnOpen: false,
   showAccountHostname: false,
   delayNotificationState: false,
+  showPills: true,
 };
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 18e0585a..f4f726a3 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -153,86 +153,88 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
             )}
             <span title={reason.description}>{reason.title}</span>
             <span>{updatedAt}</span>
-            <span className="hover:overflow-auto">
-              {notification.subject?.linkedIssues?.length > 0 && (
-                <span title={linkedIssuesPillDescription}>
-                  <button type="button" className={Constants.PILL_CLASS_NAME}>
-                    <IssueClosedIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GREEN}`}
-                      aria-label={linkedIssuesPillDescription}
-                    />
-                    {notification.subject.linkedIssues.length}
-                  </button>
-                </span>
-              )}
-              {notification.subject.reviews
-                ? notification.subject.reviews.map((review) => {
-                    const icon = getPullRequestReviewIcon(review);
-                    if (!icon) {
-                      return null;
-                    }
-
-                    return (
-                      <span key={review.state} title={icon.description}>
-                        <button
-                          type="button"
-                          className={Constants.PILL_CLASS_NAME}
-                        >
-                          <icon.type
-                            size={12}
-                            className={`mr-1 ${icon.color}`}
-                            aria-label={icon.description}
-                          />
-                          {review.users.length}
-                        </button>
-                      </span>
-                    );
-                  })
-                : null}
-              {notification.subject?.comments > 0 && (
-                <span title={commentsPillDescription}>
-                  <button type="button" className={Constants.PILL_CLASS_NAME}>
-                    <CommentIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GRAY}`}
-                      aria-label={commentsPillDescription}
-                    />
-                    {notification.subject.comments}
-                  </button>
-                </span>
-              )}
-              {notification.subject?.labels?.length > 0 && (
-                <span title={labelsPillDescription}>
-                  <button type="button" className={Constants.PILL_CLASS_NAME}>
-                    <TagIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GRAY}`}
-                      aria-label={labelsPillDescription}
-                    />
-                    {notification.subject.labels.length}
-                  </button>
-                </span>
-              )}
-              {notification.subject.milestone && (
-                <span
-                  className="ml-1"
-                  title={notification.subject.milestone.title}
-                >
-                  <button type="button" className={Constants.PILL_CLASS_NAME}>
-                    <MilestoneIcon
-                      size={12}
-                      className={
-                        notification.subject.milestone.state === 'open'
-                          ? IconColor.GREEN
-                          : IconColor.RED
+            {settings.showPills && (
+              <span className="hover:overflow-auto">
+                {notification.subject?.linkedIssues?.length > 0 && (
+                  <span title={linkedIssuesPillDescription}>
+                    <button type="button" className={Constants.PILL_CLASS_NAME}>
+                      <IssueClosedIcon
+                        size={12}
+                        className={`mr-1 ${IconColor.GREEN}`}
+                        aria-label={linkedIssuesPillDescription}
+                      />
+                      {notification.subject.linkedIssues.length}
+                    </button>
+                  </span>
+                )}
+                {notification.subject.reviews
+                  ? notification.subject.reviews.map((review) => {
+                      const icon = getPullRequestReviewIcon(review);
+                      if (!icon) {
+                        return null;
                       }
-                      aria-label={notification.subject.milestone.title}
-                    />
-                  </button>
-                </span>
-              )}
-            </span>
+
+                      return (
+                        <span key={review.state} title={icon.description}>
+                          <button
+                            type="button"
+                            className={Constants.PILL_CLASS_NAME}
+                          >
+                            <icon.type
+                              size={12}
+                              className={`mr-1 ${icon.color}`}
+                              aria-label={icon.description}
+                            />
+                            {review.users.length}
+                          </button>
+                        </span>
+                      );
+                    })
+                  : null}
+                {notification.subject?.comments > 0 && (
+                  <span title={commentsPillDescription}>
+                    <button type="button" className={Constants.PILL_CLASS_NAME}>
+                      <CommentIcon
+                        size={12}
+                        className={`mr-1 ${IconColor.GRAY}`}
+                        aria-label={commentsPillDescription}
+                      />
+                      {notification.subject.comments}
+                    </button>
+                  </span>
+                )}
+                {notification.subject?.labels?.length > 0 && (
+                  <span title={labelsPillDescription}>
+                    <button type="button" className={Constants.PILL_CLASS_NAME}>
+                      <TagIcon
+                        size={12}
+                        className={`mr-1 ${IconColor.GRAY}`}
+                        aria-label={labelsPillDescription}
+                      />
+                      {notification.subject.labels.length}
+                    </button>
+                  </span>
+                )}
+                {notification.subject.milestone && (
+                  <span
+                    className="ml-1"
+                    title={notification.subject.milestone.title}
+                  >
+                    <button type="button" className={Constants.PILL_CLASS_NAME}>
+                      <MilestoneIcon
+                        size={12}
+                        className={
+                          notification.subject.milestone.state === 'open'
+                            ? IconColor.GREEN
+                            : IconColor.RED
+                        }
+                        aria-label={notification.subject.milestone.title}
+                      />
+                    </button>
+                  </span>
+                )}
+              </span>
+            )}
           </span>
         </div>
       </div>
diff --git a/src/components/fields/Tooltip.tsx b/src/components/fields/Tooltip.tsx
index fdf89223..fa4e5517 100644
--- a/src/components/fields/Tooltip.tsx
+++ b/src/components/fields/Tooltip.tsx
@@ -19,7 +19,7 @@ export const Tooltip: FC<ITooltip> = (props: ITooltip) => {
     >
       <QuestionIcon className="text-blue-500 ml-1" />
       {showTooltip && (
-        <div className="absolute bg-white dark:bg-gray-sidebar border border-gray-300 rounded p-2 shadow">
+        <div className="absolute bg-white dark:bg-gray-sidebar border border-gray-300 rounded p-2 shadow z-10">
           <div className="text-gray-700 dark:text-white text-xs text-left">
             {props.tooltip}
           </div>
diff --git a/src/context/App.tsx b/src/context/App.tsx
index b84c8b3e..74517dae 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -53,6 +53,7 @@ export const defaultSettings: SettingsState = {
   markAsDoneOnOpen: false,
   showAccountHostname: false,
   delayNotificationState: false,
+  showPills: true,
 };
 
 interface AppContextState {
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index 25bfdb35..0d91f0dc 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -1,9 +1,14 @@
 import {
   ArrowLeftIcon,
+  CheckIcon,
+  CommentIcon,
+  IssueClosedIcon,
   KeyIcon,
+  MilestoneIcon,
   PersonIcon,
   PlusIcon,
   SignOutIcon,
+  TagIcon,
   XCircleIcon,
 } from '@primer/octicons-react';
 import { ipcRenderer } from 'electron';
@@ -150,6 +155,41 @@ export const SettingsRoute: FC = () => {
               </div>
             }
           />
+          <Checkbox
+            name="showPills"
+            label="Show notification metric pills"
+            checked={settings.showPills}
+            onChange={(evt) => updateSetting('showPills', evt.target.checked)}
+            tooltip={
+              <div>
+                <div>Show notification metric pills for:</div>
+                <div className="pl-6">
+                  <ul className="list-disc">
+                    <li>
+                      <IssueClosedIcon size={16} className="pr-1" />
+                      linked issues
+                    </li>
+                    <li>
+                      <CheckIcon size={16} className="pr-1" /> pr reviews
+                    </li>
+                    <li>
+                      <CommentIcon size={16} className="pr-1" />
+                      comments
+                    </li>
+
+                    <li>
+                      <TagIcon size={16} className="pr-1" />
+                      labels
+                    </li>
+                    <li>
+                      <MilestoneIcon size={16} className="pr-1" />
+                      milestones
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            }
+          />
           <Checkbox
             name="showAccountHostname"
             label="Show account hostname"
@@ -173,18 +213,16 @@ export const SettingsRoute: FC = () => {
             }
             tooltip={
               <div>
-                <div className="pb-3">
-                  See
-                  <button
-                    type="button"
-                    className="text-blue-500 mx-1"
-                    title="Open GitHub documentation for participating and watching notifications"
-                    onClick={openGitHubParticipatingDocs}
-                  >
-                    official docs
-                  </button>
-                  for more details.
-                </div>
+                See
+                <button
+                  type="button"
+                  className="text-blue-500 mx-1"
+                  title="Open GitHub documentation for participating and watching notifications"
+                  onClick={openGitHubParticipatingDocs}
+                >
+                  official docs
+                </button>
+                for more details.
               </div>
             }
           />
@@ -227,11 +265,9 @@ export const SettingsRoute: FC = () => {
             }
             tooltip={
               <div>
-                <div className="pb-3">
-                  Keep the notification within Gitify window upon interaction
-                  (click, mark as read, mark as done, etc) until the next
-                  refresh window (scheduled or user initiated)
-                </div>
+                Keep the notification within Gitify window upon interaction
+                (click, mark as read, mark as done, etc) until the next refresh
+                window (scheduled or user initiated)
               </div>
             }
           />
diff --git a/src/types.ts b/src/types.ts
index a3e62104..b9688f53 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -41,6 +41,7 @@ interface AppearanceSettingsState {
   theme: Theme;
   detailedNotifications: boolean;
   showAccountHostname: boolean;
+  showPills: boolean;
 }
 
 interface NotificationSettingsState {


Test patch:
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index 1f9e6f0c..51538692 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -26,7 +26,11 @@ describe('components/NotificationRow.tsx', () => {
       hostname: 'github.com',
     };
 
-    const tree = render(<NotificationRow {...props} />);
+    const tree = render(
+      <AppContext.Provider value={{ settings: mockSettings }}>
+        <NotificationRow {...props} />
+      </AppContext.Provider>,
+    );
     expect(tree).toMatchSnapshot();
   });
 
@@ -43,7 +47,11 @@ describe('components/NotificationRow.tsx', () => {
       hostname: 'github.com',
     };
 
-    const tree = render(<NotificationRow {...props} />);
+    const tree = render(
+      <AppContext.Provider value={{ settings: mockSettings }}>
+        <NotificationRow {...props} />
+      </AppContext.Provider>,
+    );
     expect(tree).toMatchSnapshot();
   });
 
@@ -60,11 +68,42 @@ describe('components/NotificationRow.tsx', () => {
       hostname: 'github.com',
     };
 
-    const tree = render(<NotificationRow {...props} />);
+    const tree = render(
+      <AppContext.Provider value={{ settings: mockSettings }}>
+        <NotificationRow {...props} />
+      </AppContext.Provider>,
+    );
     expect(tree).toMatchSnapshot();
   });
 
   describe('notification pills / metrics', () => {
+    describe('showPills disabled', () => {
+      it('should not render any pills when showPills is disabled', 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(
+          <AppContext.Provider
+            value={{
+              settings: { ...mockSettings, showPills: false },
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
+        expect(tree).toMatchSnapshot();
+      });
+    });
+
     describe('linked issue pills', () => {
       it('should render issues pill when linked to one issue/pr', async () => {
         jest
@@ -79,7 +118,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
 
@@ -96,7 +143,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
     });
@@ -115,7 +170,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
 
@@ -132,7 +195,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
 
@@ -149,7 +220,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
     });
@@ -168,7 +247,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
     });
@@ -190,7 +277,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
 
@@ -210,7 +305,15 @@ describe('components/NotificationRow.tsx', () => {
           hostname: 'github.com',
         };
 
-        const tree = render(<NotificationRow {...props} />);
+        const tree = render(
+          <AppContext.Provider
+            value={{
+              settings: mockSettings,
+            }}
+          >
+            <NotificationRow {...props} />
+          </AppContext.Provider>,
+        );
         expect(tree).toMatchSnapshot();
       });
     });
@@ -304,12 +407,10 @@ describe('components/NotificationRow.tsx', () => {
         <AppContext.Provider
           value={{
             settings: { ...mockSettings, markAsDoneOnOpen: false },
-            auth: mockAuth,
+            markNotificationRead,
           }}
         >
-          <AppContext.Provider value={{ markNotificationRead }}>
-            <NotificationRow {...props} />
-          </AppContext.Provider>
+          <NotificationRow {...props} />
         </AppContext.Provider>,
       );
 
@@ -327,14 +428,9 @@ describe('components/NotificationRow.tsx', () => {
 
       render(
         <AppContext.Provider
-          value={{
-            settings: { ...mockSettings },
-            auth: mockAuth,
-          }}
+          value={{ settings: mockSettings, markNotificationDone }}
         >
-          <AppContext.Provider value={{ markNotificationDone }}>
-            <NotificationRow {...props} />
-          </AppContext.Provider>
+          <NotificationRow {...props} />
         </AppContext.Provider>,
       );
 
@@ -352,7 +448,9 @@ describe('components/NotificationRow.tsx', () => {
 
       render(
         <AppContext.Provider value={{}}>
-          <AppContext.Provider value={{ unsubscribeNotification }}>
+          <AppContext.Provider
+            value={{ settings: mockSettings, unsubscribeNotification }}
+          >
             <NotificationRow {...props} />
           </AppContext.Provider>
         </AppContext.Provider>,
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index b9cab2c6..7cf6e5b8 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -4552,6 +4552,347 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
 }
 `;
 
+exports[`components/NotificationRow.tsx notification pills / metrics showPills disabled should not render any pills when showPills is disabled 1`] = `
+{
+  "asFragment": [Function],
+  "baseElement": <body>
+    <div>
+      <div
+        class="flex space-x-3 py-2 px-3 bg-white dark:bg-gray-dark dark:text-white hover:bg-gray-100 dark:hover:bg-gray-darker border-b border-gray-100 dark:border-gray-darker group"
+        id="138661096"
+      >
+        <div
+          class="flex justify-center items-center w-5 text-green-500"
+          title="Open Issue"
+        >
+          <svg
+            aria-label="Issue"
+            class="octicon octicon-issue-opened"
+            fill="currentColor"
+            focusable="false"
+            height="18"
+            role="img"
+            style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+            viewBox="0 0 16 16"
+            width="18"
+          >
+            <path
+              d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
+            />
+            <path
+              d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"
+            />
+          </svg>
+        </div>
+        <div
+          class="flex-1 whitespace-nowrap overflow-hidden overflow-ellipsis"
+        >
+          <div
+            class="mb-1 text-sm whitespace-nowrap cursor-pointer"
+            role="main"
+            title="I am a robot and this is a test!"
+          >
+            I am a robot and this is a test!
+          </div>
+          <div
+            class="text-xs text-capitalize"
+          >
+            <span
+              class="flex items-center gap-1"
+              title="Updated over 6 years ago"
+            >
+              <span>
+                <svg
+                  aria-hidden="true"
+                  class="text-gray-500 dark:text-gray-300"
+                  fill="currentColor"
+                  focusable="false"
+                  height="16"
+                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                  viewBox="0 0 16 16"
+                  width="16"
+                >
+                  <path
+                    d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16Zm.847-8.145a2.502 2.502 0 1 0-1.694 0C5.471 8.261 4 9.775 4 11c0 .395.145.995 1 .995h6c.855 0 1-.6 1-.995 0-1.224-1.47-2.74-3.153-3.145Z"
+                  />
+                </svg>
+              </span>
+              <span
+                title="You're watching the repository."
+              >
+                Updated
+              </span>
+              <span>
+                over 6 years ago
+              </span>
+            </span>
+          </div>
+        </div>
+        <div
+          class="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity"
+        >
+          <button
+            class="focus:outline-none h-full hover:text-green-500"
+            title="Mark as Done"
+            type="button"
+          >
+            <svg
+              aria-label="Mark as Done"
+              class="octicon octicon-check"
+              fill="currentColor"
+              focusable="false"
+              height="16"
+              role="img"
+              style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+              viewBox="0 0 16 16"
+              width="16"
+            >
+              <path
+                d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+              />
+            </svg>
+          </button>
+          <button
+            class="focus:outline-none h-full hover:text-red-500"
+            title="Unsubscribe from Thread"
+            type="button"
+          >
+            <svg
+              aria-label="Unsubscribe from Thread"
+              class="octicon octicon-bell-slash"
+              fill="currentColor"
+              focusable="false"
+              height="14"
+              role="img"
+              style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+              viewBox="0 0 16 16"
+              width="14"
+            >
+              <path
+                d="m4.182 4.31.016.011 10.104 7.316.013.01 1.375.996a.75.75 0 1 1-.88 1.214L13.626 13H2.518a1.516 1.516 0 0 1-1.263-2.36l1.703-2.554A.255.255 0 0 0 3 7.947V5.305L.31 3.357a.75.75 0 1 1 .88-1.214Zm7.373 7.19L4.5 6.391v1.556c0 .346-.102.683-.294.97l-1.703 2.556a.017.017 0 0 0-.003.01c0 .005.002.009.005.012l.006.004.007.001ZM8 1.5c-.997 0-1.895.416-2.534 1.086A.75.75 0 1 1 4.38 1.55 5 5 0 0 1 13 5v2.373a.75.75 0 0 1-1.5 0V5A3.5 3.5 0 0 0 8 1.5ZM8 16a2 2 0 0 1-1.985-1.75c-.017-.137.097-.25.235-.25h3.5c.138 0 .252.113.235.25A2 2 0 0 1 8 16Z"
+              />
+            </svg>
+          </button>
+          <button
+            class="focus:outline-none h-full hover:text-green-500"
+            title="Mark as Read"
+            type="button"
+          >
+            <svg
+              aria-label="Mark as Read"
+              class="octicon octicon-read"
+              fill="currentColor"
+              focusable="false"
+              height="14"
+              role="img"
+              style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+              viewBox="0 0 16 16"
+              width="14"
+            >
+              <path
+                d="M7.115.65a1.752 1.752 0 0 1 1.77 0l6.25 3.663c.536.314.865.889.865 1.51v6.427A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25V5.823c0-.621.33-1.196.865-1.51Zm1.011 1.293a.252.252 0 0 0-.252 0l-5.72 3.353L6.468 7.76a2.748 2.748 0 0 1 3.066 0l4.312-2.464-5.719-3.353ZM13.15 12.5 8.772 9.06a1.25 1.25 0 0 0-1.544 0L2.85 12.5Zm1.35-5.85-3.687 2.106 3.687 2.897ZM5.187 8.756 1.5 6.65v5.003Z"
+              />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  </body>,
+  "container": <div>
+    <div
+      class="flex space-x-3 py-2 px-3 bg-white dark:bg-gray-dark dark:text-white hover:bg-gray-100 dark:hover:bg-gray-darker border-b border-gray-100 dark:border-gray-darker group"
+      id="138661096"
+    >
+      <div
+        class="flex justify-center items-center w-5 text-green-500"
+        title="Open Issue"
+      >
+        <svg
+          aria-label="Issue"
+          class="octicon octicon-issue-opened"
+          fill="currentColor"
+          focusable="false"
+          height="18"
+          role="img"
+          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+          viewBox="0 0 16 16"
+          width="18"
+        >
+          <path
+            d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
+          />
+          <path
+            d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"
+          />
+        </svg>
+      </div>
+      <div
+        class="flex-1 whitespace-nowrap overflow-hidden overflow-ellipsis"
+      >
+        <div
+          class="mb-1 text-sm whitespace-nowrap cursor-pointer"
+          role="main"
+          title="I am a robot and this is a test!"
+        >
+          I am a robot and this is a test!
+        </div>
+        <div
+          class="text-xs text-capitalize"
+        >
+          <span
+            class="flex items-center gap-1"
+            title="Updated over 6 years ago"
+          >
+            <span>
+              <svg
+                aria-hidden="true"
+                class="text-gray-500 dark:text-gray-300"
+                fill="currentColor"
+                focusable="false"
+                height="16"
+                style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                viewBox="0 0 16 16"
+                width="16"
+              >
+                <path
+                  d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16Zm.847-8.145a2.502 2.502 0 1 0-1.694 0C5.471 8.261 4 9.775 4 11c0 .395.145.995 1 .995h6c.855 0 1-.6 1-.995 0-1.224-1.47-2.74-3.153-3.145Z"
+                />
+              </svg>
+            </span>
+            <span
+              title="You're watching the repository."
+            >
+              Updated
+            </span>
+            <span>
+              over 6 years ago
+            </span>
+          </span>
+        </div>
+      </div>
+      <div
+        class="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity"
+      >
+        <button
+          class="focus:outline-none h-full hover:text-green-500"
+          title="Mark as Done"
+          type="button"
+        >
+          <svg
+            aria-label="Mark as Done"
+            class="octicon octicon-check"
+            fill="currentColor"
+            focusable="false"
+            height="16"
+            role="img"
+            style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+            viewBox="0 0 16 16"
+            width="16"
+          >
+            <path
+              d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+            />
+          </svg>
+        </button>
+        <button
+          class="focus:outline-none h-full hover:text-red-500"
+          title="Unsubscribe from Thread"
+          type="button"
+        >
+          <svg
+            aria-label="Unsubscribe from Thread"
+            class="octicon octicon-bell-slash"
+            fill="currentColor"
+            focusable="false"
+            height="14"
+            role="img"
+            style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+            viewBox="0 0 16 16"
+            width="14"
+          >
+            <path
+              d="m4.182 4.31.016.011 10.104 7.316.013.01 1.375.996a.75.75 0 1 1-.88 1.214L13.626 13H2.518a1.516 1.516 0 0 1-1.263-2.36l1.703-2.554A.255.255 0 0 0 3 7.947V5.305L.31 3.357a.75.75 0 1 1 .88-1.214Zm7.373 7.19L4.5 6.391v1.556c0 .346-.102.683-.294.97l-1.703 2.556a.017.017 0 0 0-.003.01c0 .005.002.009.005.012l.006.004.007.001ZM8 1.5c-.997 0-1.895.416-2.534 1.086A.75.75 0 1 1 4.38 1.55 5 5 0 0 1 13 5v2.373a.75.75 0 0 1-1.5 0V5A3.5 3.5 0 0 0 8 1.5ZM8 16a2 2 0 0 1-1.985-1.75c-.017-.137.097-.25.235-.25h3.5c.138 0 .252.113.235.25A2 2 0 0 1 8 16Z"
+            />
+          </svg>
+        </button>
+        <button
+          class="focus:outline-none h-full hover:text-green-500"
+          title="Mark as Read"
+          type="button"
+        >
+          <svg
+            aria-label="Mark as Read"
+            class="octicon octicon-read"
+            fill="currentColor"
+            focusable="false"
+            height="14"
+            role="img"
+            style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+            viewBox="0 0 16 16"
+            width="14"
+          >
+            <path
+              d="M7.115.65a1.752 1.752 0 0 1 1.77 0l6.25 3.663c.536.314.865.889.865 1.51v6.427A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25V5.823c0-.621.33-1.196.865-1.51Zm1.011 1.293a.252.252 0 0 0-.252 0l-5.72 3.353L6.468 7.76a2.748 2.748 0 0 1 3.066 0l4.312-2.464-5.719-3.353ZM13.15 12.5 8.772 9.06a1.25 1.25 0 0 0-1.544 0L2.85 12.5Zm1.35-5.85-3.687 2.106 3.687 2.897ZM5.187 8.756 1.5 6.65v5.003Z"
+            />
+          </svg>
+        </button>
+      </div>
+    </div>
+  </div>,
+  "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 should render itself & its children 1`] = `
 {
   "asFragment": [Function],
diff --git a/src/components/fields/__snapshots__/Tooltip.test.tsx.snap b/src/components/fields/__snapshots__/Tooltip.test.tsx.snap
index 2fe780fc..d8c4bad3 100644
--- a/src/components/fields/__snapshots__/Tooltip.test.tsx.snap
+++ b/src/components/fields/__snapshots__/Tooltip.test.tsx.snap
@@ -21,7 +21,7 @@ exports[`components/fields/Tooltip.tsx should display on mouse enter / leave 1`]
     />
   </svg>
   <div
-    class="absolute bg-white dark:bg-gray-sidebar border border-gray-300 rounded p-2 shadow"
+    class="absolute bg-white dark:bg-gray-sidebar border border-gray-300 rounded p-2 shadow z-10"
   >
     <div
       class="text-gray-700 dark:text-white text-xs text-left"
diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx
index 71dc830d..8d20babc 100644
--- a/src/context/App.test.tsx
+++ b/src/context/App.test.tsx
@@ -371,7 +371,12 @@ describe('context/App.tsx', () => {
       });
 
       expect(saveStateMock).toHaveBeenCalledWith({
-        auth: { accounts: [], enterpriseAccounts: [], token: null, user: null },
+        auth: {
+          accounts: [],
+          enterpriseAccounts: [],
+          token: null,
+          user: null,
+        } as AuthState,
         settings: {
           participating: true,
           playSound: true,
@@ -384,7 +389,8 @@ describe('context/App.tsx', () => {
           markAsDoneOnOpen: false,
           showAccountHostname: false,
           delayNotificationState: false,
-        },
+          showPills: true,
+        } as SettingsState,
       });
     });
 
@@ -416,7 +422,12 @@ describe('context/App.tsx', () => {
       expect(setAutoLaunchMock).toHaveBeenCalledWith(true);
 
       expect(saveStateMock).toHaveBeenCalledWith({
-        auth: { accounts: [], enterpriseAccounts: [], token: null, user: null },
+        auth: {
+          accounts: [],
+          enterpriseAccounts: [],
+          token: null,
+          user: null,
+        } as AuthState,
         settings: {
           participating: false,
           playSound: true,
@@ -429,7 +440,8 @@ describe('context/App.tsx', () => {
           markAsDoneOnOpen: false,
           showAccountHostname: false,
           delayNotificationState: false,
-        },
+          showPills: true,
+        } as SettingsState,
       });
     });
   });
diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx
index 2edc874b..be80c903 100644
--- a/src/routes/Settings.test.tsx
+++ b/src/routes/Settings.test.tsx
@@ -127,6 +127,31 @@ describe('routes/Settings.tsx', () => {
       );
     });
 
+    it('should toggle metric pills checkbox', async () => {
+      await act(async () => {
+        render(
+          <AppContext.Provider
+            value={{
+              auth: mockAuth,
+              settings: mockSettings,
+              updateSetting,
+            }}
+          >
+            <MemoryRouter>
+              <SettingsRoute />
+            </MemoryRouter>
+          </AppContext.Provider>,
+        );
+      });
+
+      await screen.findByLabelText('Show notification metric pills');
+
+      fireEvent.click(screen.getByLabelText('Show notification metric pills'));
+
+      expect(updateSetting).toHaveBeenCalledTimes(1);
+      expect(updateSetting).toHaveBeenCalledWith('showPills', false);
+    });
+
     it('should toggle account hostname checkbox', async () => {
       await act(async () => {
         render(
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index e95556fe..3003202c 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -171,6 +171,54 @@ exports[`routes/Settings.tsx General should render itself & its children 1`] = `
           </div>
         </div>
       </div>
+      <div
+        class="mt-1 mb-3 text-sm"
+      >
+        <div
+          class="flex items-start"
+        >
+          <div
+            class="flex items-center h-5"
+          >
+            <input
+              checked=""
+              class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
+              id="showPills"
+              type="checkbox"
+            />
+          </div>
+          <div
+            class="ml-3 "
+          >
+            <label
+              class="font-medium text-gray-700 dark:text-gray-200"
+              for="showPills"
+            >
+              Show notification metric pills
+              <span
+                aria-label="tooltip-showPills"
+                class="relative"
+                id="tooltip-showPills"
+              >
+                <svg
+                  aria-hidden="true"
+                  class="text-blue-500 ml-1"
+                  fill="currentColor"
+                  focusable="false"
+                  height="16"
+                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                  viewBox="0 0 16 16"
+                  width="16"
+                >
+                  <path
+                    d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+                  />
+                </svg>
+              </span>
+            </label>
+          </div>
+        </div>
+      </div>
       <div
         class="mt-1 mb-3 text-sm"
       >