diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index c69f29d79..1f0752f22 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -10,7 +10,10 @@ import { AppContext } from '../context/App'; import { Opacity, Size } from '../types'; import type { Notification } from '../typesGitHub'; import { cn } from '../utils/cn'; -import { formatForDisplay } from '../utils/helpers'; +import { + formatForDisplay, + isMarkAsDoneFeatureSupported, +} from '../utils/helpers'; import { getNotificationTypeIcon, getNotificationTypeIconColor, @@ -126,16 +129,18 @@ export const NotificationRow: FC = ({ {!animateExit && ( - { - setAnimateExit(!settings.delayNotificationState); - setShowAsRead(settings.delayNotificationState); - markNotificationDone(notification); - }} - /> + {isMarkAsDoneFeatureSupported(notification.account) && ( + { + setAnimateExit(!settings.delayNotificationState); + setShowAsRead(settings.delayNotificationState); + markNotificationDone(notification); + }} + /> + )} = ({ {!animateExit && ( - ) => { - // Don't trigger onClick of parent element. - event.stopPropagation(); - setAnimateExit(!settings.delayNotificationState); - setShowAsRead(settings.delayNotificationState); - markRepoNotificationsDone(repoNotifications[0]); - }} - /> + {isMarkAsDoneFeatureSupported(repoNotifications[0].account) && ( + ) => { + // Don't trigger onClick of parent element. + event.stopPropagation(); + setAnimateExit(!settings.delayNotificationState); + setShowAsRead(settings.delayNotificationState); + markRepoNotificationsDone(repoNotifications[0]); + }} + /> + )} { onChange={(evt) => updateSetting('markAsDoneOnOpen', evt.target.checked) } + tooltip={ +
+ Mark as Done feature is supported in GitHub Cloud + and GitHub Enterprise Server 3.13 or later. +
+ } /> { ); return existing.settings; } + + for (const account of auth.accounts.filter( + (a) => a.platform === 'GitHub Enterprise Server', + )) { + const res = await headNotifications(account.hostname, account.token); + account.version = res.headers['x-github-enterprise-version']; + } }, []); const fetchNotificationsWithAccounts = useCallback( diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 76415d384..a5f385ab2 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -13,6 +13,7 @@ import { markRepositoryNotificationsAsRead, } from '../utils/api/client'; import { getAccountUUID } from '../utils/auth/utils'; +import { isMarkAsDoneFeatureSupported } from '../utils/helpers'; import { getAllNotifications, setTrayIconColor, @@ -120,11 +121,13 @@ export const useNotifications = (): NotificationsState => { setStatus('loading'); try { - await markNotificationThreadAsDone( - notification.id, - notification.account.hostname, - notification.account.token, - ); + if (isMarkAsDoneFeatureSupported(notification.account)) { + await markNotificationThreadAsDone( + notification.id, + notification.account.hostname, + notification.account.token, + ); + } const updatedNotifications = removeNotification( state.settings, diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap index e5b4f9869..54e98764e 100644 --- a/src/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/routes/__snapshots__/Settings.test.tsx.snap @@ -486,6 +486,26 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = ` for="markAsDoneOnOpen" > Mark as done on open + + + diff --git a/src/types.ts b/src/types.ts index e7cac771f..09ac84a8b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,6 +46,7 @@ export type Status = 'loading' | 'success' | 'error'; export interface Account { method: AuthMethod; platform: PlatformType; + version?: string; hostname: Hostname; token: Token; user: GitifyUser | null; diff --git a/src/utils/api/__snapshots__/client.test.ts.snap b/src/utils/api/__snapshots__/client.test.ts.snap index 7f0db3198..dc98f15e5 100644 --- a/src/utils/api/__snapshots__/client.test.ts.snap +++ b/src/utils/api/__snapshots__/client.test.ts.snap @@ -36,7 +36,7 @@ exports[`utils/api/client.ts headNotifications should fetch notifications head - } `; -exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - github 1`] = ` +exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - enterprise 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", @@ -45,7 +45,7 @@ exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore } `; -exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription- enterprise 1`] = ` +exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - github 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", @@ -72,7 +72,7 @@ exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list n } `; -exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - github 1`] = ` +exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - enterprise 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", @@ -81,7 +81,7 @@ exports[`utils/api/client.ts markNotificationThreadAsDone should mark notificati } `; -exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done- enterprise 1`] = ` +exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - github 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", @@ -90,7 +90,7 @@ exports[`utils/api/client.ts markNotificationThreadAsDone should mark notificati } `; -exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - github 1`] = ` +exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - enterprise 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", @@ -99,7 +99,7 @@ exports[`utils/api/client.ts markNotificationThreadAsRead should mark notificati } `; -exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read- enterprise 1`] = ` +exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - github 1`] = ` { "Accept": "application/json", "Authorization": "token token-123-456", diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts index 614758c16..edf0b0325 100644 --- a/src/utils/api/client.test.ts +++ b/src/utils/api/client.test.ts @@ -135,7 +135,7 @@ describe('utils/api/client.ts', () => { expect(axios.defaults.headers.common).toMatchSnapshot(); }); - it('should mark notification thread as read- enterprise', async () => { + it('should mark notification thread as read - enterprise', async () => { await markNotificationThreadAsRead( mockThreadId, mockEnterpriseHostname, @@ -169,7 +169,7 @@ describe('utils/api/client.ts', () => { expect(axios.defaults.headers.common).toMatchSnapshot(); }); - it('should mark notification thread as done- enterprise', async () => { + it('should mark notification thread as done - enterprise', async () => { await markNotificationThreadAsDone( mockThreadId, mockEnterpriseHostname, @@ -203,7 +203,7 @@ describe('utils/api/client.ts', () => { expect(axios.defaults.headers.common).toMatchSnapshot(); }); - it('should ignore notification thread subscription- enterprise', async () => { + it('should ignore notification thread subscription - enterprise', async () => { await ignoreNotificationThreadSubscription( mockThreadId, mockEnterpriseHostname, diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index c558b24bb..d3b358376 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -90,6 +90,8 @@ export function markNotificationThreadAsRead( * Marks a thread as "done." Marking a thread as "done" is equivalent to marking a * notification in your notification inbox on GitHub as done. * + * NOTE: This was added to GitHub Enterprise Server in version 3.13 or later. + * * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#mark-a-thread-as-done */ export function markNotificationThreadAsDone( @@ -99,7 +101,6 @@ export function markNotificationThreadAsDone( ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += `notifications/threads/${threadId}`; - return apiRequestAuth(url.toString() as Link, 'DELETE', token, {}); } diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index 805c740b4..0cdbc7b72 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -1,5 +1,9 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; -import { mockPersonalAccessTokenAccount } from '../__mocks__/state-mocks'; +import { + mockGitHubCloudAccount, + mockGitHubEnterpriseServerAccount, + mockPersonalAccessTokenAccount, +} from '../__mocks__/state-mocks'; import { defaultSettings } from '../context/App'; import type { Hostname, Link, SettingsState } from '../types'; @@ -17,6 +21,7 @@ import { getFilterCount, getPlatformFromHostname, isEnterpriseServerHost, + isMarkAsDoneFeatureSupported, } from './helpers'; describe('utils/helpers.ts', () => { @@ -56,6 +61,48 @@ describe('utils/helpers.ts', () => { }); }); + describe('isMarkAsDoneFeatureSupported', () => { + it('should return true for GitHub Cloud', () => { + expect(isMarkAsDoneFeatureSupported(mockGitHubCloudAccount)).toBe(true); + }); + + it('should return false for GitHub Enterprise Server < v3.13', () => { + const account = { + ...mockGitHubEnterpriseServerAccount, + version: '3.12.0', + }; + + expect(isMarkAsDoneFeatureSupported(account)).toBe(false); + }); + + it('should return true for GitHub Enterprise Server >= v3.13', () => { + const account = { + ...mockGitHubEnterpriseServerAccount, + version: '3.13.3', + }; + + expect(isMarkAsDoneFeatureSupported(account)).toBe(true); + }); + + it('should return false for GitHub Enterprise Server when partial version available', () => { + const account = { + ...mockGitHubEnterpriseServerAccount, + version: '3', + }; + + expect(isMarkAsDoneFeatureSupported(account)).toBe(false); + }); + + it('should return false for GitHub Enterprise Server when no version available', () => { + const account = { + ...mockGitHubEnterpriseServerAccount, + version: null, + }; + + expect(isMarkAsDoneFeatureSupported(account)).toBe(false); + }); + }); + describe('generateNotificationReferrerId', () => { it('should generate the notification_referrer_id', () => { const referrerId = generateNotificationReferrerId(mockSingleNotification); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index a0724bfe3..10094b417 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,6 @@ import { formatDistanceToNowStrict, parseISO } from 'date-fns'; import { defaultSettings } from '../context/App'; -import type { Hostname, Link, SettingsState } from '../types'; +import type { Account, Hostname, Link, SettingsState } from '../types'; import type { Notification } from '../typesGitHub'; import { getHtmlUrl, getLatestDiscussion } from './api/client'; import type { PlatformType } from './auth/types'; @@ -21,6 +21,20 @@ export function isEnterpriseServerHost(hostname: Hostname): boolean { return !hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname); } +export function isMarkAsDoneFeatureSupported(account: Account): boolean { + if (isEnterpriseServerHost(account.hostname)) { + // Support for "Mark as Done" was added to GitHub Enterprise Server in v3.13 or newer + if (account.version) { + const version = account?.version.split('.').map(Number); + return version[0] >= 3 && version[1] >= 13; + } + + return false; + } + + return true; +} + export function generateNotificationReferrerId( notification: Notification, ): string {