Repository: gitify-app/gitify
Pull request #849 (2024-03-03 11:33:15)

feat: use regex to extract all checksuite attributes
Improve match accuracy by using regex to extract all CheckSuite title attributes

setchy: Coverage check failing due to how the percentage is calculated.  All lines are covered

Patch:
diff --git a/src/typesGithub.ts b/src/typesGithub.ts
index 34fd51c5..6a2cbf9f 100644
--- a/src/typesGithub.ts
+++ b/src/typesGithub.ts
@@ -211,3 +211,11 @@ export interface DiscussionSubcommentEdge {
     createdAt: string;
   };
 }
+
+export interface CheckSuiteAttributes {
+  workflowName: string;
+  attemptNumber?: number;
+  statusDisplayName: string;
+  status: CheckSuiteStatus | null;
+  branchName: string;
+}
diff --git a/src/utils/state.ts b/src/utils/state.ts
index 60b7bf98..39a26a75 100644
--- a/src/utils/state.ts
+++ b/src/utils/state.ts
@@ -1,5 +1,6 @@
 import { formatSearchQueryString } from './helpers';
 import {
+  CheckSuiteAttributes,
   CheckSuiteStatus,
   DiscussionStateSearchResultEdge,
   DiscussionStateType,
@@ -17,7 +18,7 @@ export async function getNotificationState(
 ): Promise<StateType> {
   switch (notification.subject.type) {
     case 'CheckSuite':
-      return getCheckSuiteState(notification);
+      return getCheckSuiteAttributes(notification)?.status;
     case 'Discussion':
       return await getDiscussionState(notification, token);
     case 'Issue':
@@ -33,28 +34,46 @@ export async function getNotificationState(
  * 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 getCheckSuiteState(
+export function getCheckSuiteAttributes(
   notification: Notification,
-): CheckSuiteStatus | null {
-  const lowerTitle = notification.subject.title.toLowerCase();
-
-  if (lowerTitle.includes('cancelled for')) {
-    return 'cancelled';
-  }
-
-  if (lowerTitle.includes('failed for')) {
-    return 'failure';
+): CheckSuiteAttributes | null {
+  const regexPattern =
+    /^(?<workflowName>.*?) workflow run(, Attempt #(?<attemptNumber>\d+))? (?<statusDisplayName>.*?) for (?<branchName>.*?) branch$/;
+
+  const matches = regexPattern.exec(notification.subject.title);
+
+  if (matches) {
+    const { groups } = matches;
+
+    return {
+      workflowName: groups.workflowName,
+      attemptNumber: groups.attemptNumber
+        ? parseInt(groups.attemptNumber)
+        : null,
+      status: getCheckSuiteStatus(groups.statusDisplayName),
+      statusDisplayName: groups.statusDisplayName,
+      branchName: groups.branchName,
+    };
   }
 
-  if (lowerTitle.includes('skipped for')) {
-    return 'skipped';
-  }
+  return null;
+}
 
-  if (lowerTitle.includes('succeeded for')) {
-    return 'success';
+export function getCheckSuiteStatus(
+  statusDisplayName: string,
+): CheckSuiteStatus {
+  switch (statusDisplayName) {
+    case 'cancelled':
+      return 'cancelled';
+    case 'failed':
+      return 'failure';
+    case 'skipped':
+      return 'skipped';
+    case 'succeeded':
+      return 'success';
+    default:
+      return null;
   }
-
-  return null;
 }
 
 export async function getDiscussionState(


Test patch:
diff --git a/src/utils/state.test.ts b/src/utils/state.test.ts
index b0fc2e90..7d328aa6 100644
--- a/src/utils/state.test.ts
+++ b/src/utils/state.test.ts
@@ -4,7 +4,7 @@ import nock from 'nock';
 import { mockAccounts } from '../__mocks__/mock-state';
 import { mockedSingleNotification } from '../__mocks__/mockedData';
 import {
-  getCheckSuiteState,
+  getCheckSuiteAttributes,
   getDiscussionState,
   getIssueState,
   getPullRequestState,
@@ -26,9 +26,12 @@ describe('utils/state.ts', () => {
         },
       };
 
-      const result = getCheckSuiteState(mockNotification);
+      const result = getCheckSuiteAttributes(mockNotification);
 
-      expect(result).toBe('cancelled');
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBeNull();
+      expect(result.status).toBe('cancelled');
+      expect(result.branchName).toBe('main');
     });
 
     it('failed check suite state', async () => {
@@ -40,9 +43,29 @@ describe('utils/state.ts', () => {
         },
       };
 
-      const result = getCheckSuiteState(mockNotification);
+      const result = getCheckSuiteAttributes(mockNotification);
 
-      expect(result).toBe('failure');
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBeNull();
+      expect(result.status).toBe('failure');
+      expect(result.branchName).toBe('main');
+    });
+
+    it('multiple attempts failed check suite state', async () => {
+      const mockNotification = {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          title: 'Demo workflow run, Attempt #3 failed for main branch',
+        },
+      };
+
+      const result = getCheckSuiteAttributes(mockNotification);
+
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBe(3);
+      expect(result.status).toBe('failure');
+      expect(result.branchName).toBe('main');
     });
 
     it('skipped check suite state', async () => {
@@ -54,9 +77,12 @@ describe('utils/state.ts', () => {
         },
       };
 
-      const result = getCheckSuiteState(mockNotification);
+      const result = getCheckSuiteAttributes(mockNotification);
 
-      expect(result).toBe('skipped');
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBeNull();
+      expect(result.status).toBe('skipped');
+      expect(result.branchName).toBe('main');
     });
 
     it('successful check suite state', async () => {
@@ -68,9 +94,12 @@ describe('utils/state.ts', () => {
         },
       };
 
-      const result = getCheckSuiteState(mockNotification);
+      const result = getCheckSuiteAttributes(mockNotification);
 
-      expect(result).toBe('success');
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBeNull();
+      expect(result.status).toBe('success');
+      expect(result.branchName).toBe('main');
     });
 
     it('unknown check suite state', async () => {
@@ -78,11 +107,28 @@ describe('utils/state.ts', () => {
         ...mockedSingleNotification,
         subject: {
           ...mockedSingleNotification.subject,
-          title: 'Demo workflow run for main branch',
+          title: 'Demo workflow run unknown-status for main branch',
+        },
+      };
+
+      const result = getCheckSuiteAttributes(mockNotification);
+
+      expect(result.workflowName).toBe('Demo');
+      expect(result.attemptNumber).toBeNull();
+      expect(result.status).toBeNull();
+      expect(result.branchName).toBe('main');
+    });
+
+    it('unhandled check suite title', async () => {
+      const mockNotification = {
+        ...mockedSingleNotification,
+        subject: {
+          ...mockedSingleNotification.subject,
+          title: 'A title that is not in the structure we expect',
         },
       };
 
-      const result = getCheckSuiteState(mockNotification);
+      const result = getCheckSuiteAttributes(mockNotification);
 
       expect(result).toBeNull();
     });