diff --git a/src/js/actions/index.test.ts b/src/js/actions/index.test.ts index af317754c..d15e31661 100644 --- a/src/js/actions/index.test.ts +++ b/src/js/actions/index.test.ts @@ -373,6 +373,81 @@ describe('actions/index.js', () => { }); }); + it('should unsubscribe from a notification thread with success - github.com', () => { + const id = 123; + const hostname = 'github.com'; + + // The unsubscribe endpoint call. + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}/subscription`) + .reply(204); + + // The mark read endpoint call. + nock('https://api.github.com/') + .patch(`/notifications/threads/${id}`) + .reply(200); + + const expectedActions = [ + { type: actions.UNSUBSCRIBE_NOTIFICATION.REQUEST }, + { + type: actions.UNSUBSCRIBE_NOTIFICATION.SUCCESS, + meta: { id, hostname }, + }, + ]; + + const store = createMockStore( + { + auth: { + token: 'THISISATOKEN', + enterpriseAccounts: mockedEnterpriseAccounts, + }, + notifications: { response: [] }, + }, + expectedActions + ); + + return store + .dispatch(actions.unsubscribeNotification(id, hostname)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it('should unsubscribe from a notification thread with failure - github.com', () => { + const id = 123; + const hostname = 'github.com'; + const message = 'Oops! Something went wrong.'; + + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}/subscription`) + .reply(400, { message }); + + const expectedActions = [ + { type: actions.UNSUBSCRIBE_NOTIFICATION.REQUEST }, + { type: actions.UNSUBSCRIBE_NOTIFICATION.FAILURE, payload: { message } }, + ]; + + const store = createMockStore( + { + auth: { + token: 'THISISATOKEN', + enterpriseAccounts: mockedEnterpriseAccounts, + }, + settings: { + participating: false, + }, + notifications: { response: [] }, + }, + expectedActions + ); + + return store + .dispatch(actions.unsubscribeNotification(id, hostname)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + it('should mark a notification as read with success - github.com', () => { const id = 123; const hostname = 'github.com'; diff --git a/src/js/actions/index.ts b/src/js/actions/index.ts index 66af262a7..aabee3c01 100644 --- a/src/js/actions/index.ts +++ b/src/js/actions/index.ts @@ -18,6 +18,14 @@ export function makeAsyncActionSet(actionName) { }; } +enum Methods { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + DELETE = 'DELETE', +} + // Authentication export const LOGIN = makeAsyncActionSet('LOGIN'); @@ -27,7 +35,6 @@ export function loginUser(authOptions, code) { return (dispatch) => { const url = `https://${hostname}/login/oauth/access_token`; - const method = 'POST'; const data = { client_id: authOptions.clientId, client_secret: authOptions.clientSecret, @@ -36,7 +43,7 @@ export function loginUser(authOptions, code) { dispatch({ type: LOGIN.REQUEST }); - return apiRequest(url, method, data) + return apiRequest(url, Methods.POST, data) .then(function (response) { dispatch({ type: LOGIN.SUCCESS, @@ -60,7 +67,6 @@ export function logout(): LogoutAction { export const NOTIFICATIONS = makeAsyncActionSet('NOTIFICATIONS'); export function fetchNotifications() { return (dispatch, getState: () => AppState) => { - const method = 'GET'; const { settings }: { settings: SettingsState } = getState(); const isGitHubLoggedIn = getState().auth.token !== null; const endpointSuffix = `notifications?participating=${settings.participating}`; @@ -72,7 +78,7 @@ export function fetchNotifications() { const url = `https://api.${Constants.DEFAULT_AUTH_OPTIONS.hostname}/${endpointSuffix}`; const token = getState().auth.token; - return apiRequestAuth(url, method, token); + return apiRequestAuth(url, Methods.GET, token); } function getEnterpriseNotifications() { @@ -81,7 +87,7 @@ export function fetchNotifications() { const hostname = account.hostname; const token = account.token; const url = `https://${hostname}/api/v3/${endpointSuffix}`; - return apiRequestAuth(url, method, token); + return apiRequestAuth(url, Methods.GET, token); }); } @@ -130,7 +136,6 @@ export const MARK_NOTIFICATION = makeAsyncActionSet('MARK_NOTIFICATION'); export function markNotification(id, hostname) { return (dispatch, getState: () => AppState) => { const url = `${generateGitHubAPIUrl(hostname)}notifications/threads/${id}`; - const method = 'PATCH'; const isEnterprise = hostname !== Constants.DEFAULT_AUTH_OPTIONS.hostname; const entAccounts = getState().auth.enterpriseAccounts; @@ -140,7 +145,7 @@ export function markNotification(id, hostname) { dispatch({ type: MARK_NOTIFICATION.REQUEST }); - return apiRequestAuth(url, method, token, {}) + return apiRequestAuth(url, Methods.PATCH, token, {}) .then(function (response) { dispatch({ type: MARK_NOTIFICATION.SUCCESS, @@ -156,6 +161,48 @@ export function markNotification(id, hostname) { }; } +export const UNSUBSCRIBE_NOTIFICATION = makeAsyncActionSet( + 'UNSUBSCRIBE_NOTIFICATION' +); +export function unsubscribeNotification(id, hostname) { + return (dispatch, getState: () => AppState) => { + const markReadURL = `${generateGitHubAPIUrl( + hostname + )}notifications/threads/${id}`; + const unsubscribeURL = `${generateGitHubAPIUrl( + hostname + )}notifications/threads/${id}/subscription`; + + const isEnterprise = hostname !== Constants.DEFAULT_AUTH_OPTIONS.hostname; + const entAccounts = getState().auth.enterpriseAccounts; + const token = isEnterprise + ? getEnterpriseAccountToken(hostname, entAccounts) + : getState().auth.token; + + dispatch({ type: UNSUBSCRIBE_NOTIFICATION.REQUEST }); + + return apiRequestAuth(unsubscribeURL, Methods.DELETE, token, {}) + .then((response) => { + // The GitHub notifications API doesn't automatically mark things as read + // like it does in the UI, so after unsubscribing we also need to hit the + // endpoint to mark it as read. + return apiRequestAuth(markReadURL, Methods.PATCH, token, {}); + }) + .then((response) => { + dispatch({ + type: UNSUBSCRIBE_NOTIFICATION.SUCCESS, + meta: { id, hostname }, + }); + }) + .catch((error) => { + dispatch({ + type: UNSUBSCRIBE_NOTIFICATION.FAILURE, + payload: error.response.data, + }); + }); + }; +} + // Repo's Notification export const MARK_REPO_NOTIFICATION = makeAsyncActionSet( @@ -166,7 +213,6 @@ export function markRepoNotifications(repoSlug, hostname) { const url = `${generateGitHubAPIUrl( hostname )}repos/${repoSlug}/notifications`; - const method = 'PUT'; const isEnterprise = hostname !== Constants.DEFAULT_AUTH_OPTIONS.hostname; const entAccounts = getState().auth.enterpriseAccounts; @@ -176,7 +222,7 @@ export function markRepoNotifications(repoSlug, hostname) { dispatch({ type: MARK_REPO_NOTIFICATION.REQUEST }); - return apiRequestAuth(url, method, token, {}) + return apiRequestAuth(url, Methods.PUT, token, {}) .then(function (response) { dispatch({ type: MARK_REPO_NOTIFICATION.SUCCESS, diff --git a/src/js/components/__snapshots__/notification.test.tsx.snap b/src/js/components/__snapshots__/notification.test.tsx.snap index c1afbfa75..dfecc2448 100644 --- a/src/js/components/__snapshots__/notification.test.tsx.snap +++ b/src/js/components/__snapshots__/notification.test.tsx.snap @@ -51,6 +51,34 @@ exports[`components/notification.js should render itself & its children 1`] = ` - Updated in over 3 years +