Repository: gitify-app/gitify (ts)

Issue:
Ignore specific notifications
### 📝 Provide a description of the new feature

It would be cool if you could ignore specific notifications, such as by author of a PR. Specifically, I want to ignore notifications from @dependabot, both comments from @dependabot but also PR creations/changes from @dependabot.

### ➕ Additional Information

_No response_

Pull request:
feat(settings): ignore bot notifications
Add setting to control whether notification from `Bot` accounts are shown or hidden.

Closes #591

Patch:
diff --git a/.nvmrc b/.nvmrc
index 726a201e..c369ba60 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1,1 +1,1 @@
-20.11.1
\ No newline at end of file
+20.12.0
\ No newline at end of file
diff --git a/package.json b/package.json
index 38744998..f069c5cb 100644
--- a/package.json
+++ b/package.json
@@ -124,7 +124,7 @@
     "@types/jest": "29.5.12",
     "@types/node": "20.11.30",
     "@types/nprogress": "0.2.3",
-    "@types/react": "18.2.67",
+    "@types/react": "18.2.72",
     "@types/react-router-dom": "5.3.3",
     "@types/react-test-renderer": "18.0.7",
     "@types/react-transition-group": "4.4.10",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1fc377ce..fdb8c649 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -71,8 +71,8 @@ devDependencies:
     specifier: 0.2.3
     version: 0.2.3
   '@types/react':
-    specifier: 18.2.67
-    version: 18.2.67
+    specifier: 18.2.72
+    version: 18.2.72
   '@types/react-router-dom':
     specifier: 5.3.3
     version: 5.3.3
@@ -1217,14 +1217,14 @@ packages:
   /@types/react-dom@18.2.8:
     resolution: {integrity: sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==}
     dependencies:
-      '@types/react': 18.2.67
+      '@types/react': 18.2.72
     dev: true
 
   /@types/react-router-dom@5.3.3:
     resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
     dependencies:
       '@types/history': 4.7.11
-      '@types/react': 18.2.67
+      '@types/react': 18.2.72
       '@types/react-router': 5.1.20
     dev: true
 
@@ -1232,26 +1232,25 @@ packages:
     resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
     dependencies:
       '@types/history': 4.7.11
-      '@types/react': 18.2.67
+      '@types/react': 18.2.72
     dev: true
 
   /@types/react-test-renderer@18.0.7:
     resolution: {integrity: sha512-1+ANPOWc6rB3IkSnElhjv6VLlKg2dSv/OWClUyZimbLsQyBn8Js9Vtdsi3UICJ2rIQ3k2la06dkB+C92QfhKmg==}
     dependencies:
-      '@types/react': 18.2.67
+      '@types/react': 18.2.72
     dev: true
 
   /@types/react-transition-group@4.4.10:
     resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
     dependencies:
-      '@types/react': 18.2.67
+      '@types/react': 18.2.72
     dev: true
 
-  /@types/react@18.2.67:
-    resolution: {integrity: sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==}
+  /@types/react@18.2.72:
+    resolution: {integrity: sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg==}
     dependencies:
       '@types/prop-types': 15.7.5
-      '@types/scheduler': 0.16.4
       csstype: 3.1.2
     dev: true
 
@@ -1260,10 +1259,6 @@ packages:
     dependencies:
       '@types/node': 20.11.30
 
-  /@types/scheduler@0.16.4:
-    resolution: {integrity: sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==}
-    dev: true
-
   /@types/stack-utils@2.0.1:
     resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
     dev: true
diff --git a/src/__mocks__/mock-state.ts b/src/__mocks__/mock-state.ts
index ac5072e4..8175a7f1 100644
--- a/src/__mocks__/mock-state.ts
+++ b/src/__mocks__/mock-state.ts
@@ -11,6 +11,7 @@ export const mockSettings: SettingsState = {
   participating: false,
   playSound: true,
   showNotifications: true,
+  showBots: true,
   openAtStartup: false,
   appearance: Appearance.SYSTEM,
   colors: false,
diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts
index 987b26c7..7e05086f 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',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -309,6 +310,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T03:30:42Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -319,6 +321,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T18:26:27Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -327,6 +330,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-02-23T00:57:58Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -337,6 +341,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-23T00:57:49Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -347,6 +352,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-27T01:22:20Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -355,6 +361,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:43:52Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -365,6 +372,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-04T20:39:44Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -373,6 +381,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:04Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -383,6 +392,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-05T11:05:42Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -391,6 +401,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:44Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -412,6 +423,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-20T18:33:39Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -422,6 +434,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T03:30:42Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -432,6 +445,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-21T18:26:27Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -440,6 +454,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-02-23T00:57:58Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -450,6 +465,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-23T00:57:49Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [],
@@ -460,6 +476,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-02-27T01:22:20Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -468,6 +485,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:43:52Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -478,6 +496,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-04T20:39:44Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -486,6 +505,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:04Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
@@ -496,6 +516,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                   createdAt: '2022-03-05T11:05:42Z',
                   author: {
                     login: 'comment-user',
+                    type: 'User',
                   },
                   replies: {
                     nodes: [
@@ -504,6 +525,7 @@ export const mockedGraphQLResponse: GraphQLSearch<Discussion> = {
                         createdAt: '2022-03-05T17:41:44Z',
                         author: {
                           login: 'reply-user',
+                          type: 'User',
                         },
                       },
                     ],
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 54a42474..9cf2664a 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -80,7 +80,7 @@ export const NotificationRow: React.FC<IProps> = ({
       </div>
 
       <div
-        className="flex-1 overflow-hidden"
+        className="flex-1 overflow-hidden cursor-pointer"
         onClick={() => pressTitle()}
         role="main"
       >
diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx
index 6c7a4ce5..933ce6a4 100644
--- a/src/components/Repository.tsx
+++ b/src/components/Repository.tsx
@@ -47,7 +47,9 @@ export const RepositoryNotifications: React.FC<IProps> = ({
           ) : (
             <MarkGithubIcon size={18} />
           )}
-          <span onClick={openBrowser}>{repoName}</span>
+          <span className="cursor-pointer" onClick={openBrowser}>
+            {repoName}
+          </span>
         </div>
 
         <div className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity">
diff --git a/src/context/App.tsx b/src/context/App.tsx
index 2f7b8487..3da7ba37 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -34,6 +34,7 @@ export const defaultSettings: SettingsState = {
   participating: false,
   playSound: true,
   showNotifications: true,
+  showBots: true,
   openAtStartup: false,
   appearance: Appearance.SYSTEM,
   colors: null,
@@ -91,7 +92,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
 
   useEffect(() => {
     fetchNotifications(accounts, settings);
-  }, [settings.participating]);
+  }, [settings.participating, settings.showBots]);
 
   useEffect(() => {
     fetchNotifications(accounts, settings);
diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts
index 5da2b46d..2bb7f15b 100644
--- a/src/hooks/useNotifications.ts
+++ b/src/hooks/useNotifications.ts
@@ -128,32 +128,48 @@ export const useNotifications = (colors: boolean): NotificationsState => {
                 data.map(async (accountNotifications) => {
                   return {
                     hostname: accountNotifications.hostname,
-                    notifications: await axios.all<Notification>(
-                      accountNotifications.notifications.map(
-                        async (notification: Notification) => {
-                          const isEnterprise = isEnterpriseHost(
-                            accountNotifications.hostname,
-                          );
-                          const token = isEnterprise
-                            ? getEnterpriseAccountToken(
-                                accountNotifications.hostname,
-                                accounts.enterpriseAccounts,
-                              )
-                            : accounts.token;
-
-                          const additionalSubjectDetails =
-                            await getGitifySubjectDetails(notification, token);
-
-                          return {
-                            ...notification,
-                            subject: {
-                              ...notification.subject,
-                              ...additionalSubjectDetails,
-                            },
-                          };
-                        },
-                      ),
-                    ),
+                    notifications: await axios
+                      .all<Notification>(
+                        accountNotifications.notifications.map(
+                          async (notification: Notification) => {
+                            const isEnterprise = isEnterpriseHost(
+                              accountNotifications.hostname,
+                            );
+                            const token = isEnterprise
+                              ? getEnterpriseAccountToken(
+                                  accountNotifications.hostname,
+                                  accounts.enterpriseAccounts,
+                                )
+                              : accounts.token;
+
+                            const additionalSubjectDetails =
+                              await getGitifySubjectDetails(
+                                notification,
+                                token,
+                              );
+
+                            return {
+                              ...notification,
+                              subject: {
+                                ...notification.subject,
+                                ...additionalSubjectDetails,
+                              },
+                            };
+                          },
+                        ),
+                      )
+                      .then((notifications) => {
+                        return notifications.filter((notification) => {
+                          if (
+                            !settings.showBots &&
+                            notification.subject?.user.type === 'Bot'
+                          ) {
+                            return false;
+                          }
+
+                          return true;
+                        });
+                      }),
                   };
                 }),
               )
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index eaff01b8..1c218bac 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -89,7 +89,7 @@ export const SettingsRoute: React.FC = () => {
       className="flex flex-1 flex-col dark:bg-gray-dark dark:text-white"
       data-testid="settings"
     >
-      <div className="flex justify-between items-center mt-4 py-2 mx-8">
+      <div className="flex justify-between items-center mt-2 py-2 mx-8">
         <button
           className="focus:outline-none"
           title="Go Back"
@@ -146,6 +146,12 @@ export const SettingsRoute: React.FC = () => {
             updateSetting('showNotifications', evt.target.checked)
           }
         />
+        <FieldCheckbox
+          name="showBots"
+          label="Show notifications from Bot accounts"
+          checked={settings.showBots}
+          onChange={(evt) => updateSetting('showBots', evt.target.checked)}
+        />
         <FieldCheckbox
           name="markAsDoneOnOpen"
           label="Mark as done on open"
@@ -172,7 +178,7 @@ export const SettingsRoute: React.FC = () => {
         )}
       </div>
 
-      <div className="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-4 px-8">
+      <div className="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-1 px-8">
         <small
           className="font-semibold cursor-pointer"
           title="View release notes"
diff --git a/src/types.ts b/src/types.ts
index 5e0eee68..a3147c0f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -10,6 +10,7 @@ export interface SettingsState {
   participating: boolean;
   playSound: boolean;
   showNotifications: boolean;
+  showBots: boolean;
   openAtStartup: boolean;
   appearance: Appearance;
   colors: boolean | null;
diff --git a/src/typesGithub.ts b/src/typesGithub.ts
index 2c640ef8..01afa62c 100644
--- a/src/typesGithub.ts
+++ b/src/typesGithub.ts
@@ -137,10 +137,12 @@ export interface User {
 
 export interface SubjectUser {
   login: string;
+  type: string;
 }
 
 export interface DiscussionAuthor {
   login: string;
+  type: string;
 }
 
 export interface Repository {
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 91d1dd9f..f7832428 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -160,6 +160,7 @@ export async function fetchDiscussion(
           createdAt
           author {
             login
+            type
           }
         }
       
diff --git a/src/utils/subject.ts b/src/utils/subject.ts
index e651c1c1..8fb565b5 100644
--- a/src/utils/subject.ts
+++ b/src/utils/subject.ts
@@ -10,6 +10,7 @@ import {
   PullRequest,
   PullRequestStateType,
   ReleaseComments,
+  SubjectUser,
   User,
   WorkflowRunAttributes,
 } from '../typesGithub';
@@ -110,9 +111,12 @@ async function getGitifySubjectForDiscussion(
   const latestDiscussionComment = getLatestDiscussionComment(
     discussion.comments.nodes,
   );
-  let discussionUser = null;
+  let discussionUser: SubjectUser = null;
   if (latestDiscussionComment) {
-    discussionUser = latestDiscussionComment.author;
+    discussionUser = {
+      login: latestDiscussionComment.author.login,
+      type: latestDiscussionComment.author.type,
+    };
   }
 
   return {
@@ -135,6 +139,7 @@ async function getGitifySubjectForIssue(
     state: issue.state_reason ?? issue.state,
     user: {
       login: issueCommentUser?.login ?? issue.user.login,
+      type: issueCommentUser?.type ?? issue.user.type,
     },
   };
 }
@@ -160,6 +165,7 @@ async function getGitifySubjectForPullRequest(
     state: prState,
     user: {
       login: prCommentUser?.login ?? pr.user.login,
+      type: prCommentUser?.type ?? pr.user.type,
     },
   };
 }
@@ -174,6 +180,7 @@ async function getGitifySubjectForRelease(
     state: null,
     user: {
       login: releaseCommentUser.login,
+      type: releaseCommentUser.type,
     },
   };
 }


Test patch:
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index 035a7722..9605e5d8 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -138,14 +138,14 @@ describe('components/NotificationRow.tsx', () => {
       hostname: 'github.com',
     };
 
-    const { getByLabelText } = render(
+    const { getByTitle } = render(
       <AppContext.Provider value={{}}>
         <AppContext.Provider value={{ unsubscribeNotification }}>
           <NotificationRow {...props} />
         </AppContext.Provider>
       </AppContext.Provider>,
     );
-    fireEvent.click(getByLabelText('Unsubscribe'));
+    fireEvent.click(getByTitle('Unsubscribe'));
     expect(unsubscribeNotification).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index b671daa4..52d225d9 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -35,7 +35,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
     </svg>
   </div>
   <div
-    className="flex-1 overflow-hidden"
+    className="flex-1 overflow-hidden cursor-pointer"
     onClick={[Function]}
     role="main"
   >
diff --git a/src/components/__snapshots__/Repository.test.tsx.snap b/src/components/__snapshots__/Repository.test.tsx.snap
index a47cf4cb..934a3a31 100644
--- a/src/components/__snapshots__/Repository.test.tsx.snap
+++ b/src/components/__snapshots__/Repository.test.tsx.snap
@@ -13,6 +13,7 @@ exports[`components/Repository.tsx should render itself & its children 1`] = `
         src="https://avatars0.githubusercontent.com/u/6333409?v=3"
       />
       <span
+        className="cursor-pointer"
         onClick={[Function]}
       >
         manosim/gitify
@@ -123,6 +124,7 @@ exports[`components/Repository.tsx should use default repository icon when avata
         />
       </svg>
       <span
+        className="cursor-pointer"
         onClick={[Function]}
       >
         manosim/gitify
diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx
index 6f704f9f..63d0e8bf 100644
--- a/src/context/App.test.tsx
+++ b/src/context/App.test.tsx
@@ -290,6 +290,7 @@ describe('context/App.tsx', () => {
         participating: true,
         playSound: true,
         showNotifications: true,
+        showBots: true,
         colors: null,
         markAsDoneOnOpen: false,
       },
@@ -328,6 +329,7 @@ describe('context/App.tsx', () => {
         participating: false,
         playSound: true,
         showNotifications: true,
+        showBots: true,
         colors: null,
         markAsDoneOnOpen: false,
       },
diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts
index bfa23ffe..549861c7 100644
--- a/src/hooks/useNotifications.test.ts
+++ b/src/hooks/useNotifications.test.ts
@@ -303,16 +303,30 @@ describe('hooks/useNotifications.ts', () => {
 
         nock('https://api.github.com')
           .get('/3')
-          .reply(200, { state: 'closed', merged: true });
+          .reply(200, {
+            state: 'closed',
+            merged: true,
+            user: {
+              login: 'some-user',
+              type: 'User',
+            },
+          });
         nock('https://api.github.com')
           .get('/3/comments')
-          .reply(200, { user: { login: 'some-user' } });
+          .reply(200, { user: { login: 'some-commenter', type: 'User' } });
         nock('https://api.github.com')
           .get('/4')
-          .reply(200, { state: 'closed', merged: false });
+          .reply(200, {
+            state: 'closed',
+            merged: false,
+            user: {
+              login: 'some-user',
+              type: 'User',
+            },
+          });
         nock('https://api.github.com')
           .get('/4/comments')
-          .reply(200, { user: { login: 'some-user' } });
+          .reply(200, { user: { login: 'some-commenter', type: 'User' } });
 
         const { result } = renderHook(() => useNotifications(true));
 
@@ -332,6 +346,84 @@ describe('hooks/useNotifications.ts', () => {
         expect(result.current.notifications[0].notifications.length).toBe(6);
       });
     });
+
+    describe('showBots', () => {
+      it('should hide bot notifications when set to false', async () => {
+        const accounts: AuthState = {
+          ...mockAccounts,
+          enterpriseAccounts: [],
+          user: mockedUser,
+        };
+
+        const notifications = [
+          {
+            id: 1,
+            subject: {
+              title: 'This is an Issue.',
+              type: 'Issue',
+              url: 'https://api.github.com/1',
+              latest_comment_url: null,
+            },
+            repository: {
+              full_name: 'some/repo',
+            },
+          },
+          {
+            id: 2,
+            subject: {
+              title: 'This is a Pull Request.',
+              type: 'PullRequest',
+              url: 'https://api.github.com/2',
+              latest_comment_url: null,
+            },
+            repository: {
+              full_name: 'some/repo',
+            },
+          },
+        ];
+
+        nock('https://api.github.com')
+          .get('/notifications?participating=false')
+          .reply(200, notifications);
+        nock('https://api.github.com')
+          .get('/1')
+          .reply(200, {
+            state: 'closed',
+            merged: true,
+            user: {
+              login: 'some-user',
+              type: 'User',
+            },
+          });
+        nock('https://api.github.com')
+          .get('/2')
+          .reply(200, {
+            state: 'closed',
+            merged: false,
+            user: {
+              login: 'some-bot',
+              type: 'Bot',
+            },
+          });
+
+        const { result } = renderHook(() => useNotifications(true));
+
+        act(() => {
+          result.current.fetchNotifications(accounts, {
+            ...mockSettings,
+            showBots: false,
+          });
+        });
+
+        expect(result.current.isFetching).toBe(true);
+
+        await waitFor(() => {
+          expect(result.current.notifications[0].hostname).toBe('github.com');
+        });
+
+        expect(result.current.notifications[0].notifications.length).toBe(1);
+      });
+    });
   });
 
   describe('removeNotificationFromState', () => {
diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx
index d0b71382..3e36c9da 100644
--- a/src/routes/Settings.test.tsx
+++ b/src/routes/Settings.test.tsx
@@ -127,6 +127,34 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('participating', false);
   });
 
+  it('should toggle the showBots checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider
+          value={{
+            settings: mockSettings,
+            accounts: mockAccounts,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
+
+    fireEvent.click(getByLabelText('Show notifications from Bot accounts'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('showBots', false);
+  });
+
   it('should toggle the playSound checkbox', async () => {
     let getByLabelText;
 
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index 272f9be4..2fa0d6d3 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -34,7 +34,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
   data-testid="settings"
 >
   <div
-    class="flex justify-between items-center mt-4 py-2 mx-8"
+    class="flex justify-between items-center mt-2 py-2 mx-8"
   >
     <button
       class="focus:outline-none"
@@ -206,6 +206,30 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
         </label>
       </div>
     </div>
+    <div
+      class="flex items-start mt-1 mb-3"
+    >
+      <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="showBots"
+          type="checkbox"
+        />
+      </div>
+      <div
+        class="ml-3 text-sm"
+      >
+        <label
+          class="font-medium text-gray-700 dark:text-gray-200"
+          for="showBots"
+        >
+          Show notifications from Bot accounts
+        </label>
+      </div>
+    </div>
     <div
       class="flex items-start mt-1 mb-3"
     >
@@ -278,7 +302,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
     </div>
   </div>
   <div
-    class="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-4 px-8"
+    class="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-1 px-8"
   >
     <small
       class="font-semibold cursor-pointer"