Repository: gitify-app/gitify
Pull request #850 (2024-03-03 12:04:28)

feat: use regex to extract workflow-run attributes
Use regex to match workflow run titles and extract attributes.  Will support https://github.com/gitify-app/gitify/issues/526

setchy: > I don't use GitHub to handle deployments, so I've actually never seen this type of notification, but code wise everything looks good!

Me either! I created it in my sandbox - https://github.com/setchy/gitify-notifications-sandbox/actions/workflows/waiting.yml

If/as we see other formats of WorkflowRun notifications, we can add support then.

Patch:
diff --git a/src/typesGithub.ts b/src/typesGithub.ts
index 6a2cbf9f..d051ab67 100644
--- a/src/typesGithub.ts
+++ b/src/typesGithub.ts
@@ -219,3 +219,9 @@ export interface CheckSuiteAttributes {
   status: CheckSuiteStatus | null;
   branchName: string;
 }
+
+export interface WorkflowRunAttributes {
+  user: string;
+  statusDisplayName: string;
+  status: CheckSuiteStatus | null;
+}
diff --git a/src/utils/state.ts b/src/utils/state.ts
index 39a26a75..4d2b79c6 100644
--- a/src/utils/state.ts
+++ b/src/utils/state.ts
@@ -9,6 +9,7 @@ import {
   Notification,
   PullRequestStateType,
   StateType,
+  WorkflowRunAttributes,
 } from '../typesGithub';
 import { apiRequestAuth } from './api-requests';
 
@@ -25,6 +26,8 @@ export async function getNotificationState(
       return await getIssueState(notification, token);
     case 'PullRequest':
       return await getPullRequestState(notification, token);
+    case 'WorkflowRun':
+      return getWorkflowRunAttributes(notification)?.status;
     default:
       return null;
   }
@@ -59,9 +62,7 @@ export function getCheckSuiteAttributes(
   return null;
 }
 
-export function getCheckSuiteStatus(
-  statusDisplayName: string,
-): CheckSuiteStatus {
+function getCheckSuiteStatus(statusDisplayName: string): CheckSuiteStatus {
   switch (statusDisplayName) {
     case 'cancelled':
       return 'cancelled';
@@ -151,3 +152,37 @@ export async function getPullRequestState(
 
   return pr.state;
 }
+
+/**
+ * Ideally we would be using a GitHub API to fetch the CheckSuite / WorkflowRun state,
+ * but there isn't an obvious/clean way to do this currently.
+ */
+export function getWorkflowRunAttributes(
+  notification: Notification,
+): WorkflowRunAttributes | null {
+  const regexPattern =
+    /^(?<user>.*?) requested your (?<statusDisplayName>.*?) to deploy to an environment$/;
+
+  const matches = regexPattern.exec(notification.subject.title);
+
+  if (matches) {
+    const { groups } = matches;
+
+    return {
+      user: groups.user,
+      status: getWorkflowRunStatus(groups.statusDisplayName),
+      statusDisplayName: groups.statusDisplayName,
+    };
+  }
+
+  return null;
+}
+
+function getWorkflowRunStatus(statusDisplayName: string): CheckSuiteStatus {
+  switch (statusDisplayName) {
+    case 'review':
+      return 'waiting';
+    default:
+      return null;
+  }
+}


Test patch:
diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts
index 30fb6dcf..6e245c9c 100644
--- a/src/hooks/useNotifications.test.ts
+++ b/src/hooks/useNotifications.test.ts
@@ -237,6 +237,14 @@ describe('hooks/useNotifications.ts', () => {
               url: 'https://api.github.com/5',
             },
           },
+          {
+            id: 6,
+            subject: {
+              title: 'This is a workflow run.',
+              type: 'WorkflowRun',
+              url: 'https://api.github.com/6',
+            },
+          },
         ];
 
         nock('https://api.github.com')
@@ -286,7 +294,7 @@ describe('hooks/useNotifications.ts', () => {
           expect(result.current.notifications[0].hostname).toBe('github.com');
         });
 
-        expect(result.current.notifications[0].notifications.length).toBe(5);
+        expect(result.current.notifications[0].notifications.length).toBe(6);
       });
     });
   });
diff --git a/src/utils/state.test.ts b/src/utils/state.test.ts
index 7d328aa6..0335dde1 100644
--- a/src/utils/state.test.ts
+++ b/src/utils/state.test.ts
@@ -8,6 +8,7 @@ import {
   getDiscussionState,
   getIssueState,
   getPullRequestState,
+  getWorkflowRunAttributes,
 } from './state';
 describe('utils/state.ts', () => {
   beforeEach(() => {
@@ -518,4 +519,51 @@ describe('utils/state.ts', () => {
       expect(result).toBe('open');
     });
   });
+
+  describe('getWorkflowRunState', () => {
+    it('deploy review workflow run state', async () => {
+      const mockNotification = {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          title: 'some-user requested your review to deploy to an environment',
+        },
+      };
+
+      const result = getWorkflowRunAttributes(mockNotification);
+
+      expect(result.user).toBe('some-user');
+      expect(result.status).toBe('waiting');
+    });
+
+    it('unknown workflow run state', async () => {
+      const mockNotification = {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          title:
+            'some-user requested your unknown-state to deploy to an environment',
+        },
+      };
+
+      const result = getWorkflowRunAttributes(mockNotification);
+
+      expect(result.user).toBe('some-user');
+      expect(result.status).toBeNull();
+    });
+
+    it('unhandled workflow run title', async () => {
+      const mockNotification = {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          title: 'unhandled workflow run structure',
+        },
+      };
+
+      const result = getWorkflowRunAttributes(mockNotification);
+
+      expect(result).toBeNull();
+    });
+  });
 });