-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Slack integration #9
base: master
Are you sure you want to change the base?
Changes from all commits
68d0d6b
d521835
e271964
ad9b1ff
4dc84ed
dbae959
0eec6c7
60eaf02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default { | ||
apiToken: process.env.SLACK_API_TOKEN || '', | ||
channel: process.env.SLACK_NOTIFICATIONS_CHANNEL, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { NotificationsController } from './notifications.controller'; | ||
|
||
describe('Notifications Controller', () => { | ||
let controller: NotificationsController; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
controllers: [NotificationsController], | ||
}).compile(); | ||
|
||
controller = module.get<NotificationsController>(NotificationsController); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(controller).toBeDefined(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Controller, Post, Query } from '@nestjs/common'; | ||
|
||
import { UserWorklogResult } from '../aggregator/interfaces/user-worklog-result.interface'; | ||
import { AggregatorService } from '../aggregator/aggregator.service'; | ||
import { CalamariService } from '../calamari/calamari.service'; | ||
|
||
import { NotificationsService } from './notifications.service'; | ||
|
||
@Controller('notifications') | ||
export class NotificationsController { | ||
constructor( | ||
private readonly aggregatorService: AggregatorService, | ||
private readonly calamariService: CalamariService, | ||
private readonly notificationsService: NotificationsService, | ||
) { | ||
} | ||
|
||
@Post('/slack/to-users') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Post by default returns 201 Created. I am not sure that this should do this. |
||
public async sendNotificationsToUsers(@Query('date') date: string) { | ||
const lastWorkingDate = await this.calamariService.previousWorkingDay(new Date(date)); | ||
const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate); | ||
const lazyUsers = users.filter(user => !user.worklogs.length); | ||
|
||
lazyUsers.forEach(workLogResult => this.notificationsService.sendToUser(workLogResult, lastWorkingDate)); | ||
} | ||
|
||
@Post('/slack/to-channel') | ||
public async sendNotificationToChannel(@Query('date') date: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Post by default returns 201 Created. I am not sure that this should do this. |
||
const lastWorkingDate = await this.calamariService.previousWorkingDay(new Date(date)); | ||
const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think, should we move worklogs aggregation to NotificationService? I think it's service work. |
||
|
||
const lazyUsers = users.filter(user => !user.worklogs.length); | ||
|
||
await this.notificationsService.sendToChannel(lazyUsers, lastWorkingDate); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Module } from '@nestjs/common'; | ||
|
||
import { AggregatorModule } from '../aggregator/aggregator.module'; | ||
import { CalamariModule } from '../calamari/calamari.module'; | ||
|
||
import { NotificationsController } from './notifications.controller'; | ||
import { NotificationsService } from './notifications.service'; | ||
import { SlackService } from './slack/slack.service'; | ||
|
||
@Module({ | ||
imports: [AggregatorModule, CalamariModule], | ||
controllers: [NotificationsController], | ||
providers: [NotificationsService, SlackService], | ||
}) | ||
export class NotificationsModule { | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be moved to the same line, but it's nothing special :) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { NotificationsService } from './notifications.service'; | ||
|
||
describe('NotificationsService', () => { | ||
let service: NotificationsService; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [NotificationsService], | ||
}).compile(); | ||
|
||
service = module.get<NotificationsService>(NotificationsService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { ConfigService } from 'nestjs-config'; | ||
|
||
import { UserWorklogResult } from '../aggregator/interfaces/user-worklog-result.interface'; | ||
|
||
import { SlackService } from './slack/slack.service'; | ||
|
||
@Injectable() | ||
export class NotificationsService { | ||
constructor( | ||
private readonly slackService: SlackService, | ||
private readonly config: ConfigService, | ||
) { | ||
} | ||
|
||
public sendToUser(workLogResult: UserWorklogResult, date: Date) { | ||
const dateString = date.toLocaleDateString('pl', { | ||
year: 'numeric', | ||
month: 'long', | ||
day: 'numeric', | ||
}); | ||
|
||
const message = `Cześć ${workLogResult.firstName} :wave:. Uzupełnij work log za ${dateString}. Dzięki!`; | ||
|
||
return this.slackService.sendToUser(message, workLogResult.email); | ||
} | ||
|
||
public sendToChannel(workLogResults: UserWorklogResult[], date: Date) { | ||
const dateString = date.toLocaleDateString('pl', { | ||
year: 'numeric', | ||
month: 'long', | ||
day: 'numeric', | ||
}); | ||
const message = `:alert: Brakujące work logi za *${dateString}* :alert: \n\n${workLogResults | ||
.map(result => [`:pisiorek: ${result.firstName} ${result.lastName}`]) | ||
.join('\n')}`; | ||
|
||
return this.slackService.sendToChannel(message, this.config.get('slack.channel')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { SlackService } from './slack.service'; | ||
|
||
describe('SlackService', () => { | ||
let service: SlackService; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [SlackService], | ||
}).compile(); | ||
|
||
service = module.get<SlackService>(SlackService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { BadRequestException, Injectable } from '@nestjs/common'; | ||
import { WebAPICallResult, WebClient } from '@slack/web-api'; | ||
import { ConfigService } from 'nestjs-config'; | ||
|
||
@Injectable() | ||
export class SlackService { | ||
private readonly webClient: WebClient; | ||
|
||
private readonly channel: string; | ||
|
||
constructor( | ||
private readonly configService: ConfigService, | ||
) { | ||
this.webClient = new WebClient(configService.get('slack.apiToken')); | ||
this.channel = configService.get('slack.channel'); | ||
} | ||
|
||
public async sendToChannel(message: string, channel = null): Promise<WebAPICallResult> { | ||
try { | ||
return this.webClient.chat.postMessage({ | ||
channel: channel || this.channel, | ||
text: message, | ||
}); | ||
} catch (e) { | ||
throw new BadRequestException(`Unable to post a message to channel ${channel}`); | ||
} | ||
} | ||
|
||
public async sendToUser(message: string, email: string): Promise<WebAPICallResult> { | ||
let user; | ||
try { | ||
const userResponse = await this.webClient.users.lookupByEmail({ | ||
email, | ||
}); | ||
user = userResponse.user; | ||
} catch (e) { | ||
throw new BadRequestException(`Unable to lookup user by email '${email}'`); | ||
} | ||
|
||
try { | ||
return await this.webClient.chat.postMessage({ | ||
channel: user.id, | ||
text: message, | ||
}); | ||
} catch (e) { | ||
throw new BadRequestException(`Unable to post a message to ${email}`); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.