Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/__mocks__/mockedData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountNotifications, EnterpriseAccount } from '../types';
import { AccountNotifications, AuthState, EnterpriseAccount } from '../types';
import { Notification, Repository, User, GraphQLSearch } from '../typesGithub';

export const mockedEnterpriseAccounts: EnterpriseAccount[] = [
Expand All @@ -14,6 +14,12 @@ export const mockedUser: User = {
id: 123456789,
};

export const mockedAuthState: AuthState = {
token: '1234568790',
enterpriseAccounts: mockedEnterpriseAccounts,
user: mockedUser,
};

// prettier-ignore
export const mockedSingleNotification: Notification = {
id: '138661096',
Expand Down
22 changes: 9 additions & 13 deletions src/utils/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import {
generateGitHubWebUrl,
generateGitHubAPIUrl,
generateNotificationReferrerId,
getCommentId,
getLatestDiscussionCommentId,
isEnterpriseHost,
} from './helpers';
import {
mockedSingleNotification,
mockedUser,
mockedGraphQLResponse,
mockedAuthState,
} from '../__mocks__/mockedData';

const URL = {
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('utils/helpers.ts', () => {
mockedUser.id,
);
expect(referrerId).toBe(
'notification_referrer_id=MDE4Ok5vdGlmaWNhdGlvblRocmVhZDEzODY2MTA5NjoxMjM0NTY3ODk=',
'MDE4Ok5vdGlmaWNhdGlvblRocmVhZDEzODY2MTA5NjoxMjM0NTY3ODk=',
);
});
});
Expand Down Expand Up @@ -109,8 +109,8 @@ describe('utils/helpers.ts', () => {
testGenerateUrl(
`${URL.normal.api}/issues/5`,
`${URL.normal.default}/issues/5?${notificationReferrerId}#issuecomment-1059824632`,
'#issuecomment-' +
getCommentId(`${URL.normal.api}/issues/comments/1059824632`),
// '#issuecomment-' +
// getCommentId(`${URL.normal.api}/issues/comments/1059824632`),
));

it('should generate the GitHub discussion url with correct commentId', () =>
Expand All @@ -124,15 +124,11 @@ describe('utils/helpers.ts', () => {
));

function testGenerateUrl(apiUrl, ExpectedResult, comment?) {
const notif = { ...mockedSingleNotification, subject: { url: apiUrl } };
expect(
generateGitHubWebUrl(
notif.subject.url,
notif.id,
mockedUser.id,
comment,
),
).toBe(ExpectedResult);
const notif = {
...mockedSingleNotification,
subject: { ...mockedSingleNotification.subject, url: apiUrl },
};
expect(generateGitHubWebUrl(notif, mockedAuthState)).toBe(ExpectedResult);
}
});

Expand Down
145 changes: 61 additions & 84 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,29 @@ export function generateGitHubAPIUrl(hostname) {
: `https://api.${hostname}/`;
}

export function generateNotificationReferrerId(
export function addNotificationReferrerIdToUrl(
url: string,
notificationId: string,
userId: number,
) {
const buffer = Buffer.from(
`018:NotificationThread${notificationId}:${userId}`,
): string {
const parsedUrl = new URL(url);

parsedUrl.searchParams.set(
'notification_referrer_id',
generateNotificationReferrerId(notificationId, userId),
);
return `notification_referrer_id=${buffer.toString('base64')}`;

return parsedUrl.href;
}

export function generateGitHubWebUrl(
url: string,
export function generateNotificationReferrerId(
notificationId: string,
userId?: number,
comment: string = '',
) {
const { hostname } = new URL(url);
const isEnterprise = isEnterpriseHost(hostname);

let newUrl: string = isEnterprise
? url.replace(`${hostname}/api/v3/repos`, hostname)
: url.replace('api.github.com/repos', 'github.com');

if (newUrl.indexOf('/pulls/') !== -1) {
newUrl = newUrl.replace('/pulls/', '/pull/');
}

if (userId) {
const notificationReferrerId = generateNotificationReferrerId(
notificationId,
userId,
);

return `${newUrl}?${notificationReferrerId}${comment}`;
}

return newUrl + comment;
userId: number,
): string {
const buffer = Buffer.from(
`018:NotificationThread${notificationId}:${userId}`,
);
return buffer.toString('base64');
}

const addHours = (date: string, hours: number) =>
Expand All @@ -71,18 +57,18 @@ const addHours = (date: string, hours: number) =>
const queryString = (repo: string, title: string, lastUpdated: string) =>
`${title} in:title repo:${repo} updated:>${addHours(lastUpdated, -2)}`;

async function getReleaseTagWebUrl(notification: Notification, token: string) {
const response = await apiRequestAuth(notification.subject.url, 'GET', token);
export async function getHtmlUrl(url: string, token: string) {
const response = await apiRequestAuth(url, 'GET', token);

return {
url: response.data.html_url,
};
return response.data.html_url;
}

async function getDiscussionUrl(
notification: Notification,
token: string,
): Promise<{ url: string; latestCommentId: string | number }> {
): Promise<string> {
let url = `${notification.repository.url}/discussions`;

const response: GraphQLSearch = await apiRequestAuth(
`https://api.github.com/graphql`,
'POST',
Expand Down Expand Up @@ -132,17 +118,19 @@ async function getDiscussionUrl(
(edge) => edge.node.viewerSubscription === 'SUBSCRIBED',
);

let comments = edges[0]?.node.comments.edges;
if (edges[0]) {
url = edges[0].node.url;

let latestCommentId: string | number;
if (comments?.length) {
latestCommentId = getLatestDiscussionCommentId(comments);
let comments = edges[0]?.node.comments.edges;

let latestCommentId: string | number;
if (comments?.length) {
latestCommentId = getLatestDiscussionCommentId(comments);
url += `#discussioncomment-${latestCommentId}`;
}
}

return {
url: edges[0]?.node.url,
latestCommentId,
};
return url;
}

export const getLatestDiscussionCommentId = (
Expand All @@ -154,49 +142,38 @@ export const getLatestDiscussionCommentId = (
.reduce((a, b) => (a.node.createdAt > b.node.createdAt ? a : b))?.node
.databaseId;

export const getCommentId = (url: string) =>
/comments\/(?<id>\d+)/g.exec(url)?.groups?.id;

export async function openInBrowser(
export async function generateGitHubWebUrl(
notification: Notification,
accounts: AuthState,
) {
if (notification.subject.type === 'Release') {
getReleaseTagWebUrl(notification, accounts.token).then(({ url }) =>
openExternalLink(
generateGitHubWebUrl(
url,
notification.id,
accounts.user?.id,
undefined,
),
),
);
} else if (notification.subject.type === 'Discussion') {
getDiscussionUrl(notification, accounts.token).then(
({ url, latestCommentId }) =>
openExternalLink(
generateGitHubWebUrl(
url || `${notification.repository.url}/discussions`,
notification.id,
accounts.user?.id,
latestCommentId
? '#discussioncomment-' + latestCommentId
: undefined,
),
),
);
} else if (notification.subject.url) {
const latestCommentId = getCommentId(
let url = notification.repository.html_url;

if (notification.subject.latest_comment_url) {
url = await getHtmlUrl(
notification.subject.latest_comment_url,
accounts.token,
);
openExternalLink(
generateGitHubWebUrl(
notification.subject.url,
notification.id,
accounts.user?.id,
latestCommentId ? '#issuecomment-' + latestCommentId : undefined,
),
);
} else if (notification.subject.url) {
url = await getHtmlUrl(notification.subject.url, accounts.token);
}

// Perform any specific notification type handling (only required for a few special notification scenarios)
switch (notification.subject.type) {
case 'Discussion':
url = await getDiscussionUrl(notification, accounts.token);
break;
}

url = addNotificationReferrerIdToUrl(url, notification.id, accounts.user?.id);

return url;
}

export async function openInBrowser(
notification: Notification,
accounts: AuthState,
) {
const url = await generateGitHubWebUrl(notification, accounts);

openExternalLink(url);
}