diff --git a/src/app/components/options/options.component.ts b/src/app/components/options/options.component.ts index c32a9b9..568be8f 100644 --- a/src/app/components/options/options.component.ts +++ b/src/app/components/options/options.component.ts @@ -42,7 +42,7 @@ export class OptionsComponent extends DisposableComponent implements OnInit { this.settings = settingsService.getExtensionSettings(); - this.enableBrowserNotifications = !!this.settings.notifications.browser; + this.enableBrowserNotifications = this.settings.notifications.browser; this.enableSlackNotifications = !!this.settings.notifications.slack; this.slackSettings = this.settings.notifications.slack || new SlackSettings(); this.bitbucketSettings = this.settings.bitbucket || new BitbucketSettings(); @@ -163,16 +163,7 @@ export class OptionsComponent extends DisposableComponent implements OnInit { } onBrowserNotificationTest() { - this.notificationService - .requestPermission() - .subscribe(permission => { - if (permission === 'granted') { - new Notification('test', { - icon: 'icon64.png', - body: 'hello' - }); - } - }); + this.notificationService.sendBrowserNotification('test', 'hello', 'https://www.mozilla.org') } } diff --git a/src/app/other/notification.titles.ts b/src/app/other/notification.titles.ts index a3cbc3a..8401ac2 100644 --- a/src/app/other/notification.titles.ts +++ b/src/app/other/notification.titles.ts @@ -37,7 +37,7 @@ export function GetSlackNotificationTitle(options: NotificationOptions): string return title; } -export function GetWindowsNotificationBody(action: PullRequestActivityAction) { +export function GetWindowsNotificationBody(action: PullRequestActivityAction): string { let body; switch (action) { case PullRequestActivityAction.Commented: @@ -64,6 +64,9 @@ export function GetWindowsNotificationBody(action: PullRequestActivityAction) { case PullRequestActivityAction.Conflicted: body = 'pull request got code conflicts'; break; + default: + body = `an action taken over pull request: ${action}`; + break; } return body; diff --git a/src/app/other/slack-notification.builder.ts b/src/app/other/slack-notification.builder.ts new file mode 100644 index 0000000..093b223 --- /dev/null +++ b/src/app/other/slack-notification.builder.ts @@ -0,0 +1,111 @@ +import {NotificationOptions, PullRequestIssue} from '../models/models'; +import {GetSlackNotificationTitle} from './notification.titles'; +import {SlackMessageOptions} from '../services/slackClient'; +import {PullRequestActivityAction} from '../models/enums'; + +export class SlackNotificationBuilder { + private readonly message: SlackMessageOptions; + + constructor(private memberId: string, private options: NotificationOptions, private issues: PullRequestIssue[]) { + let title = GetSlackNotificationTitle(this.options); + + this.message = { + channel: this.memberId, + text: title, + blocks: [] + }; + + // add title with PR link + // todo: use link to the comment for Commented action + this.message.blocks?.push({ + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `${title}: *<${options.pullRequest.links.self[0].href}|${options.pullRequest.title}>*` + } + }); + } + + private addComment() { + if (this.options.action == PullRequestActivityAction.Commented) { + this.message.blocks?.push({ + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `_${this.options.comment?.text}_` + } + }); + } + + return this; + } + + private addDescription() { + let descriptionMaxLength = 50; + if (this.options.pullRequest.description?.length) { + let prDescription: string; + if (this.options.action === PullRequestActivityAction.Opened) { + // use longer description for just created PR + descriptionMaxLength = 200; + } + + // todo: fix description + let spaceIndex = this.options.pullRequest.description.indexOf(' ', descriptionMaxLength); + prDescription = this.options.pullRequest.description.length > descriptionMaxLength && spaceIndex > descriptionMaxLength + ? (this.options.pullRequest.description.substr(0, spaceIndex + 1)) + '...' + : this.options.pullRequest.description; + + this.message.blocks?.push({ + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `*Description:*\n${prDescription}\n` + } + }); + } + return this; + } + + private addIssues() { + if (this.issues.length) { + this.message.blocks?.push({ + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `*Issues:* ${this.issues.map(i => `<${i.url}|${i.key}>`).join(', ')}` + } + }); + } + return this; + } + + private addContext() { + const data = this.options.pullRequest; + this.message.blocks?.push({ + 'type': 'context', + 'elements': [ + { + 'text': + `*PR created by <${data.author.user.links?.self[0].href}|${data.author.user.displayName}>` + + ` at ${new Date(data.createdDate).toDateString()}*` + + ` | <${data.fromRef.repository.links?.self[0].href}|${data.fromRef.repository.name}>` + + ` \`${data.toRef.displayId}\`` + + `${data.properties.commentCount ? (` :memo:${data.properties.commentCount}`) : ''}`, + 'type': 'mrkdwn' + } + ] + }); + return this; + } + + public build(): SlackMessageOptions { + this + .addComment() + // .addDescription() + .addIssues() + .addContext(); + + this.message.blocks?.push({'type': 'divider'}); + return this.message; + } +} diff --git a/src/app/services/notification.service.ts b/src/app/services/notification.service.ts index 597c602..da948ab 100644 --- a/src/app/services/notification.service.ts +++ b/src/app/services/notification.service.ts @@ -1,16 +1,15 @@ import {Injectable} from '@angular/core'; import {DataService} from './data.service'; -import {SlackClient, SlackMessageOptions} from './slackClient'; +import {SlackClient} from './slackClient'; import {catchError} from 'rxjs/operators'; import {from, Observable, of, throwError} from 'rxjs'; -import {NotificationOptions, PullRequestIssue} from '../models/models'; +import {NotificationOptions} from '../models/models'; import {BitbucketService} from './bitbucket.service'; -import {PullRequestActivityAction} from '../models/enums'; -import {GetSlackNotificationTitle, GetWindowsNotificationBody} from '../other/notification.titles'; +import {GetWindowsNotificationBody} from '../other/notification.titles'; +import {SlackNotificationBuilder} from '../other/slack-notification.builder'; @Injectable() export class NotificationService { - constructor(private dataService: DataService, private bitbucketService: BitbucketService) { } @@ -22,8 +21,28 @@ export class NotificationService { return of(Notification.permission); } - sendNotification(options: NotificationOptions) { + sendBrowserNotification(title: string, body: string, clickUrl?: string) { + this + .requestPermission() + .subscribe((permission: NotificationPermission) => { + if (permission === 'granted') { + let notification = new Notification(title, { + icon: 'icon64.png', + body: body + }); + + if (clickUrl) { + notification.onclick = (event) => { + // prevent the browser from focusing the Notification's tab + event.preventDefault(); + window.open(clickUrl, '_blank'); + }; + } + } + }); + } + sendNotification(options: NotificationOptions) { // skip notification if it is snoozed for this PR let snoozeSettings = this.dataService.getNotificationSnoozeSettings(); if (snoozeSettings.includes(options.pullRequest.id)) { @@ -31,19 +50,9 @@ export class NotificationService { } let extensionSettings = this.dataService.getExtensionSettings(); - if (extensionSettings.notifications.browser) { - this.requestPermission() - .subscribe(permission => { - if (permission === 'granted') { - let body = GetWindowsNotificationBody(options.action); - - new Notification(options.pullRequest.title, { - icon: 'favicon.ico', - body: body - }); - } - }); + let body = GetWindowsNotificationBody(options.action); + this.sendBrowserNotification(options.pullRequest.title, body, options.pullRequest.links.self[0].href); } // check if slack is enabled @@ -57,7 +66,7 @@ export class NotificationService { .getPullRequestIssues(pr.fromRef.repository.project.key, pr.fromRef.repository.slug, pr.id) .pipe(catchError(() => of([]))) .subscribe(issues => { - let messageOptions = NotificationService.buildPullRequestSlackMessage(slackSettings.memberId, options, issues); + let messageOptions = new SlackNotificationBuilder(slackSettings.memberId, options, issues).build(); slackClient .postMessage(messageOptions) @@ -93,88 +102,4 @@ export class NotificationService { chrome.browserAction.setTitle({title: options.title}); } } - - public static buildPullRequestSlackMessage(memberId: string, options: NotificationOptions, issues: PullRequestIssue[]): SlackMessageOptions { - let title = GetSlackNotificationTitle(options); - let data = options.pullRequest; - - let message: SlackMessageOptions = { - channel: memberId, - text: title, - blocks: [] - }; - - // add title with PR link - // todo: use link to the comment for Commented action - message.blocks?.push({ - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `${title}: *<${data.links.self[0].href}|${data.title}>*` - } - }); - - // add comment - if (options.action == PullRequestActivityAction.Commented) { - message.blocks?.push({ - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `_${options.comment?.text}_` - } - }); - } - - // add description - let descriptionMaxLength = 50; - if (data.description?.length) { - let prDescription: string; - if (options.action === PullRequestActivityAction.Opened) { - // use longer description for just created PR - descriptionMaxLength = 200; - } - let spaceIndex = data.description.indexOf(' ', descriptionMaxLength); - prDescription = data.description.length > descriptionMaxLength && spaceIndex > descriptionMaxLength - ? (data.description.substr(0, spaceIndex + 1)) + '...' - : data.description; - - message.blocks?.push({ - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `*Description:*\n${prDescription}\n` - } - }); - } - - // add issues - if (issues.length) { - message.blocks?.push({ - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `*Issues:* ${issues.map(i => `<${i.url}|${i.key}>`).join(', ')}` - } - }); - } - - // add context - message.blocks?.push({ - 'type': 'context', - 'elements': [ - { - 'text': - `*PR created by <${data.author.user.links?.self[0].href}|${data.author.user.displayName}>` + - ` at ${new Date(data.createdDate).toDateString()}*` + - ` | <${data.fromRef.repository.links?.self[0].href}|${data.fromRef.repository.name}>` + - ` \`${data.toRef.displayId}\`` + - `${data.properties.commentCount ? (` :memo:${data.properties.commentCount}`) : ''}`, - 'type': 'mrkdwn' - } - ] - }); - - message.blocks?.push({'type': 'divider'}); - return message; - } }